I want to draw a line segment using cocos2d-js. Suppose I have two points
cc.p(50,50);
and
cc.p(200,200);
, how do I connect these two points using a line?
I've looked around and
cc.DrawNode();
is not having a drawLine() method though it has a drawDot() method. So I thought I should go for
cc.DrawingPrimitiveCanvas(renderContext);
but I can't seem to get it working. I am only starting to learn cocos2d-js. It would be very helpful if you can show me how to draw a simple line, be it using DrawNode() or DrawingPrimitiveCanvas().
I got the answer.
var line = new cc.DrawNode();
line.drawSegment(cc.p(50,50), cc.p(200,200),2);
I should've been looking for the 'drawSegment' method within 'DrawNode'.
Here's the usage:
drawSegment(from, to, lineWidth, color)
//draw a segment with a radius and color
Parameters:
{cc.Point} from,
{cc.Point} to,
{Number} lineWidth,
{cc.Color} color
Related
I'm using Angular-Chart-js for my website to display some types of graphs, one of them is a line chart.
I would like the line chart to be color-filled, but with different colors according to the y-axis' value. Like in this photo:
I've tried to have different data arrays in the "data" array of the graph, the first one has all the values, the second one has all but the ones painted in green (on the right), the third is the same array only until the purple range etc. and then have for each dataset its own color, but eventually I get a graph with a single color according to the last dataset color.
What am I missing? Is there any way to accomplish that?
Thanks.
Unfortunately, you cannot achieve this with the current chart.js configuration options. The reason is because the line chart backgroundColor option (the option that controls the color of the line chart fill) only accepts a single value.
After digging through the current chart.js 2.5 source, I found that it is possible to extend the line element's draw() method and force chart.js to use a canvas linear gradient for the fill (instead of just a single color). With a little bit of math, we can convert the x position of each point into a linear gradient color stop position and build a gradient.
With this enhancement, you can now pass in an array of colors to the line chart backgroundColor option to achieve varying colored fill regions. Here is an example of what a resulting chart would look like.
Here is how to actually do it (with a working example at the bottom)
First, we must extend Chart.elements.Line and overwrite it's draw() method so that we can build the linear gradient based upon the position of each point, use it as the line fill, and then draw the line.
// save the original line element so we can still call it's
// draw method after we build the linear gradient
var origLineElement = Chart.elements.Line;
// define a new line draw method so that we can build a linear gradient
// based on the position of each point
Chart.elements.Line = Chart.Element.extend({
draw: function() {
var vm = this._view;
var backgroundColors = this._chart.controller.data.datasets[this._datasetIndex].backgroundColor;
var points = this._children;
var ctx = this._chart.ctx;
var minX = points[0]._model.x;
var maxX = points[points.length - 1]._model.x;
var linearGradient = ctx.createLinearGradient(minX, 0, maxX, 0);
// iterate over each point to build the gradient
points.forEach(function(point, i) {
// `addColorStop` expects a number between 0 and 1, so we
// have to normalize the x position of each point between 0 and 1
// and round to make sure the positioning isn't too percise
// (otherwise it won't line up with the point position)
var colorStopPosition = roundNumber((point._model.x - minX) / (maxX - minX), 2);
// special case for the first color stop
if (i === 0) {
linearGradient.addColorStop(0, backgroundColors[i]);
} else {
// only add a color stop if the color is different
if (backgroundColors[i] !== backgroundColors[i-1]) {
// add a color stop for the prev color and for the new color at the same location
// this gives a solid color gradient instead of a gradient that fades to the next color
linearGradient.addColorStop(colorStopPosition, backgroundColors[i - 1]);
linearGradient.addColorStop(colorStopPosition, backgroundColors[i]);
}
}
});
// save the linear gradient in background color property
// since this is what is used for ctx.fillStyle when the fill is rendered
vm.backgroundColor = linearGradient;
// now draw the lines (using the original draw method)
origLineElement.prototype.draw.apply(this);
}
});
Then, we have to also extend the line chart to ensure that the line element used by the chart is the one that we extended above (since this property is already set at load time)
// we have to overwrite the datasetElementType property in the line controller
// because it is set before we can extend the line element (this ensures that
// the line element used by the chart is the one that we extended above)
Chart.controllers.line = Chart.controllers.line.extend({
datasetElementType: Chart.elements.Line,
});
With this done, we can now pass in an array of colors to the line chart backgroundColor property (instead of just a single value) to control the line fill.
Here is a codepen example that demonstrates all that has been discussed.
Caveats:
This approach could break in future chart.js releases since we are messing with the internals.
I'm not familiar with angular-chart.js, so I cannot provide insight on how to integrate the above chart.js changes into the angular directive.
If you would like to have this capability with angular2 and ng2-charts there maybe a less "hacked" way to do this but this is how I was able to apply Jordan's code to make it work:
Downgrade ng2-chart's dependency on Chart.js from 2.7.x to 2.5.
- From your project's directory: npm install chart.js#2.5 --save
Inside node_modules/chart.js/src/charts:
- Add Jordan's code to Chart.line.js( inside the export ) after the Chart.line function
Rebuild Chart.js/dist:
- run npm install
run gulp build
If you get an error from socket.io code, then you will need to upgrade those dependencies to a more current version of socket.io, I believe Karma might have an old version of socket.io that you could upgrade to 2.0.
Anyway this worked for me. It is not fully tested and it is definitely a "hack" but I did not want to learn Chart.js 2.7 to figure out why Jordan's code would not work with it. Which is definitely the more "proper" way to do it. I suppose it should be integrated as a "plugin".
i decided to do the chartJS 2.5 approach but use the extension above vs modifying the chartjs code itself..
i have to work on some performance optimization, as my charts have over 4000 values.
getting the color array built with the right values (sparse alternate color, maybe for 200 in 4000 values) and then having the extension read it to build the linear gradient is very time consuming. buries the raspberry PI I am using for the chart display system.
I finally decided that to reduce the processing time, I needed to eliminate any extra processing of the list of points.. mine collect, mine creating the color array , and chart building the linear grandient...
so, now I create the linearGradient edges as I go thru the data (all in one pass).. the gradient is an array of structures, that have offset from start of data array, and the color to be applied to that edge, basically does what the original extension does.. so, reduce 800 points to 40 edges. or 800 points to 1 edge (start)...
so, here is my updated extend function.. my app has charts with all three color types,. single fixed, array of colors and the array of edges.. all the other routines above are unchanged
// save the original line element so we can still call it's
// draw method after we build the linear gradient
var origLineElement = Chart.elements.Line;
// define a new line draw method so that we can build a linear gradient
// based on the position of each point
Chart.elements.Line = Chart.Element.extend({
draw: function() {
var vm = this._view;
var backgroundColors = this._chart.controller.data.datasets[this._datasetIndex].backgroundColor;
var points = this._children;
var ctx = this._chart.ctx;
var minX = points[0]._model.x;
var maxX = points[points.length - 1]._model.x;
var linearGradient = ctx.createLinearGradient(minX, 0, maxX, 0);
// if not a single color
if( typeof backgroundColors != 'string' ){
// but is array of colors
if( typeof backgroundColors[0] === 'string' ) {
// iterate over each point to build the gradient
points.forEach(function(point, i) { // start original code
// `addColorStop` expects a number between 0 and 1, so we
// have to normalize the x position of each point between 0 and 1
// and round to make sure the positioning isn't too percise
// (otherwise it won't line up with the point position)
var colorStopPosition = self.roundNumber((point._model.x - minX) / (maxX - minX), 2);
// special case for the first color stop
if (i === 0) {
linearGradient.addColorStop(0, backgroundColors[i]);
} else {
// only add a color stop if the color is different
if ( backgroundColors[i] !== backgroundColors[i-1]) {
// add a color stop for the prev color and for the new color at the same location
// this gives a solid color gradient instead of a gradient that fades to the next color
linearGradient.addColorStop(colorStopPosition, backgroundColors[i - 1]);
linearGradient.addColorStop(colorStopPosition, backgroundColors[i]);
}
}
}); // end original code
} // end of if for color array
// must be a gradient fence position list
else {
// loop thru the fence positions
backgroundColors.forEach(function(fencePosition){
var colorStopPosition = self.roundNumber(fencePosition.offset / points.length, 2);
linearGradient.addColorStop(colorStopPosition,fencePosition.color)
});
} // end of block for handling color space edges
// save the linear gradient in background color property
// since this is what is used for ctx.fillStyle when the fill is rendered
vm.backgroundColor = linearGradient;
} // end of if for just one color
// now draw the lines (using the original draw method)
origLineElement.prototype.draw.apply(this);
}
first of all im not only new in HTML5 or Canvas im new in the whole Coding Process. I used this Example http://rectangleworld.com/demos/DustySphere/DustySphere.html and tried to modify it in a way i can use it for my needs.
At the moment it looks like this.
code is on codepen
Now i tried to to combine different settings and generate 2 different Particle Animations at the same time. For example: the Particles on 2050 and 2070 should appear on 2090 combined. Is there any easy solution for this problem? I appreciate every help.
Draw particles with differing settings by using a function
Since you're new to coding, I'm wondering if you know about functions.
A function is a reusable block of code.
You can send arguments for a function to use.
Arguments can make the function apply different setting while executing its code.
Here's a quick example using a single function that accepts different arguments to draw your particles with different settings.
In general, you can apply different setting by creating a function that accepts parameters to draw the varied settings. That way you can call that same function multiple times with different setting.
// draw a small red circle at [x=50,y=50] with full alpha
drawParticle(50,50,10,'red',1.00);
// draw a large green circle at [x=150,y=100] with half alpha
drawParticle(150,100,50,'green',0.50);
function drawParticle(cx,cy,radius,color,alpha){
ctx.beginPath();
ctx.arc(cx,cy,radius,0,Math.PI*2);
ctx.fillStyle=color;
ctx.globalAlpha=alpha;
ctx.fill();
}
For more complex settings you can use if statements to draw the varied settings
For example, this function lets you draw a particle that "ages" from 0 to 100.
Pass in the particle's age as an argument and the function uses if statements to reduce the particle's alpha as it ages.
function drawParticle(cx,cy,radius,color,age){
ctx.beginPath();
ctx.arc(cx,cy,radius,0,Math.PI*2);
ctx.fillStyle=color;
if(age<25){
ctx.globalAlpha=1.00;
}else if(age<50){
ctx.globalAlpha=.75;
}else if(age<75){
ctx.globalAlpha=.50;
}else{
ctx.globalAlpha=.25;
}
ctx.fill();
}
I'm letting the user click on two points on a sphere and I would then like to draw a line between the two points along the surface of the sphere (basically on the great circle). I've been able to get the coordinates of the two selected points and draw a QuadraticBezierCurve3 between the points, but I need to be using CubicBezierCurve3. The problem is is that I have no clue how to find the two control points.
Part of the issue is everything I find is for circular arcs and only deals with [x,y] coordinates (whereas I'm working with [x,y,z]). I found this other question which I used to get a somewhat-working solution using QuadraticBezierCurve3. I've found numerous other pages with math/code like this, this, and this, but I really just don't know what to apply. Something else I came across mentioned the tangents (to the selected points), their intersection, and their midpoints. But again, I'm unsure of how to do that in 3D space (since the tangent can go in more than one direction, i.e. a plane).
An example of my code: http://jsfiddle.net/GhB82/
To draw the line, I'm using:
function drawLine(point) {
var middle = [(pointA['x'] + pointB['x']) / 2, (pointA['y'] + pointB['y']) / 2, (pointA['z'] + pointB['z']) / 2];
var curve = new THREE.QuadraticBezierCurve3(new THREE.Vector3(pointA['x'], pointA['y'], pointA['z']), new THREE.Vector3(middle[0], middle[1], middle[2]), new THREE.Vector3(pointB['x'], pointB['y'], pointB['z']));
var path = new THREE.CurvePath();
path.add(curve);
var curveMaterial = new THREE.LineBasicMaterial({
color: 0xFF0000
});
curvedLine = new THREE.Line(path.createPointsGeometry(20), curveMaterial);
scene.add(curvedLine);
}
Where pointA and pointB are arrays containing the [x,y,z] coordinates of the selected points on the sphere. I need to change the QuadraticBezierCurve3 to CubicBezierCurve3, but again, I'm really at a loss on finding those control points.
I have a description on how to fit cubic curves to circular arcs over at http://pomax.github.io/bezierinfo/#circles_cubic, the 3D case is essentially the same in that you need to find out the (great) circular cross-section your two points form on the sphere, and then build the cubic Bezier section along that circle.
Downside: Unless your arc is less than or equal to roughly a quarter circle, one curve is not going to be enough, you'll need two or more. You can't actually model true circular curves with Bezier curves, so using cubic instead of quadratic just means you can approximate a longer arc segment before it starts to look horribly off.
So on a completely different solution note: if you have an arc command available, much better to use that than to roll your own (and if three.js doesn't support them, definitely worth filing a feature request for, I'd think)
I've been able to construct labeled donut chart just like in the following fiddle:
http://jsfiddle.net/MX7JC/9/
But now I'm trying to place the label in the middle of each arc and to span them along the arc (curve the label to follow each arc). To do that I've been thinking of putting the svg:text along svg:textPath using the d3.svg.line.radial function.
Then I stumbled upon the following fiddle:
http://jsfiddle.net/Wexcode/CrDUy/
However I'm having difficulty to tie the var arcs (the one having actual data) from the former fiddle with the var line from the latter fiddle as the latter fiddle uses the d3.range function as the data.
I've been doing trial-and-error for hours but nothing works. Does anyone know how the d3.svg.line.radial works together with the d3.svg.arc?
The d3.svg.line.radial function constructs a series of cubic Bezier curves (not arcs) between multiple points in an array based on input polar coordinates (radius and angle) for each point.
(The example you linked to appears to draw a circle, but only because it breaks the circle down to many tightly spaced points -- try using 5 points instead of 50, and you'll see that the shape of the curve isn't a real circle.)
The d3.svg.arc function contstructs a shape consisting of two con-centric arcs and the straight lines connecting them, based on values for innerRadius, outerRadius, startAngle and endAngle.
Both methods specify angles in radians starting from "12 o'clock" (vertical pointing up). However, there are a couple difficulties in getting the radial line function to work with the arc data objects.
The first problem is that the line generator expects to be passed an array of multiple points, not a single object. In order to ger around that, you'll have to set the datum of the path element to be an array of the arc group's object repeated twice, once for the start and once for the end of the arc, and then use a function in i to determine whether the startAngle or the endAngle should be used for the angle value of each point.
Here's a variation of your fiddle creating those paths. I haven't bothered getting the text to run along the path, I'm just drawing the paths in black:
http://jsfiddle.net/MX7JC/688/
Now you see the second problem: if only given two points, the line generator will just create a straight line between them.
See simple curve example: http://jsfiddle.net/4VnHn/5/
In order to get any kind of curve with the default line generators, you need to add additional points to act as control points, and change the line interpolate method to an "open" option so that the end control points aren't drawn. I found that making the start and end control points 45 degrees beyond the start and end points of the curve (around the circle) created a curve that was acceptably similar to an arc in my simple example.
See better simple curve example: http://jsfiddle.net/4VnHn/6/
For your visualization, the curves generator now has to be passed the data object repeated four times in an array, and the angle accessor is now going to need a switch statement to figure out the different points: http://jsfiddle.net/MX7JC/689/
The results are acceptable for the small donut segments, but not for the ones that are wider than 45 degrees themselves -- in these cases, the control points end up so far around the around the circle that they throw off the curve completely. The curve generator doesn't know anything about the circle, it's just trying to smoothly connect the points to show a trend from one to the next.
A better solution is to actually draw an arc, using the arc notation for SVG paths. The arc generator uses arc notation, but it creates the full two-dimensional shape. To create arcs with a line generator, you're going to need a custom line interpolator function which you can then pass to the line generator's interpolate method.
The line generator will execute the custom line interpolator function, passing in an array of points that have already been converted from polar coordinates to x,y coordinates. From there you need to define the arc equation. Because an arc function also need to know the radius for the arc, I use a nested function -- the outside function accepts the radius as a parameter and returns the function that will accept the points array as a parameter:
function arcInterpolator(r) {
//creates a line interpolator function
//which will draw an arc of radius `r`
//between successive polar coordinate points on the line
return function(points) {
//the function must return a path definition string
//that can be appended after a "M" command
var allCommands = [];
var startAngle; //save the angle of the previous point
//in order to allow comparisons to determine
//if this is large arc or not, clockwise or not
points.forEach(function(point, i) {
//the points passed in by the line generator
//will be two-element arrays of the form [x,y]
//we also need to know the angle:
var angle = Math.atan2(point[0], point[1]);
//console.log("from", startAngle, "to", angle);
var command;
if (i) command = ["A", //draw an arc from the previous point to this point
r, //x-radius
r, //y-radius (same as x-radius for a circular arc)
0, //angle of ellipse (not relevant for circular arc)
+(Math.abs(angle - startAngle) > Math.PI),
//large arc flag,
//1 if the angle change is greater than 180degrees
// (pi radians),
//0 otherwise
+(angle < startAngle), //sweep flag, draws the arc clockwise
point[0], //x-coordinate of new point
point[1] //y-coordinate of new point
];
else command = point; //i = 0, first point of curve
startAngle = angle;
allCommands.push( command.join(" ") );
//convert to a string and add to the command list
});
return allCommands.join(" ");
};
}
Live example: http://jsfiddle.net/4VnHn/8/
To get it to work with your donut graph, I started with the version above that was producing straight lines, and changed the interpolate parameter of the line generator to use my custom function. The only additional change I had to make was to add an extra check to make sure none of the angles on the graph ended up more than 360 degrees (which I'm sure was just a rounding issue on the last arc segment, but was causing my function to draw the final arc the entire way around the circle, backwards):
var curveFunction = d3.svg.line.radial()
.interpolate( arcInterpolator(r-45) )
.tension(0)
.radius(r-45)
.angle(function(d, i) {
return Math.min(
i? d.endAngle : d.startAngle,
Math.PI*2
);
//if i is 1 (true), this is the end of the curve,
//if i is 0 (false), this is the start of the curve
});
Live example: http://jsfiddle.net/MX7JC/690/
Finally, to use these curves as text paths:
set the curve to have no stroke and no fill;
give each curve a unique id value based on your data categories
(for your example, you could use the donut label plus the data label to come up with something like "textcurve-Agg-Intl");
add a <textPath> element for each label;
set the text paths' xlink:href attribute to be # plus the same unique id value for that data
I thought of a different approach. It's slightly roundabout way to do it, but requires a lot less custom code.
Instead of creating a custom line interpolator to draw the arc, use a d3 arc generator to create the curve definition for an entire pie segment, and then use regular expressions to extract the curve definition for just the outside curve of the pie.
Simplified example here: http://jsfiddle.net/4VnHn/10/
Example with the donut chart here: http://jsfiddle.net/MX7JC/691/
Key code:
var textArc = d3.svg.arc().outerRadius(r-45); //to generate the arcs for the text
textCurves.attr("d", function(d) {
var pie = textArc(d); //get the path code for the entire pie piece
var justArc = /[Mm][\d\.\-e,\s]+[Aa][\d\.\-e,\s]+/;
//regex that matches a move statement followed by an arc statement
return justArc.exec(pie)[0];
//execute regular expression and extract matched part of string
});
The r-45 is just halfway between the inner and outer radii of the donuts. The [\d\.\-e,\s]+ part of the regular expression matches digits, periods, negative signs, exponent indicators ('e'), commas or whitespace, but not any of the other letters which signify a different type of path command. I think the rest is pretty self-explanatory.
I have a set of Raphael paths that I want to animate when a separate path is hovered over.
The client sent an SVG illustration that I had converted into Raphael code. This is just a small section of the larger image.
What I'm trying to do is as follows:
starting canvas
Start with a set of path objects in a line. When you hover over the red path they animate in a spiral until they form
ending canvas
I've done some research, and I think that I'm going to have to animate each circle along a hidden path that depicts the arcs they have to move to get to the final spiral shape (Animate along a path), but I'm not sure if this is the most efficient way to create this animation.
On top of that, I'm not really sure how to calculate the angle and size of the hidden paths the circles would be following. What is going to be the best way to create this animation?
Thanks!
I think moving each circle along a hidden path is easiest way.
I would suggest to create a new SVG file in InkScape vector editor, draw circles in start and end positions and then connect them with arcs. Save the file. Then open the file in any text editor and copy all data to your JS code (copy path's "d" parameter and coordinates of circles).
It's been more than one year since it's asked but maybe it might help other people.
Using simple algebra thought in high school you can define two functions: linear and spiral.
function linear(i){
x = i*15;
y = x;
return {cx:x+offset,cy:y+offset};
}
function spiral(i){
r = 50 - i*5;
x = r*Math.cos(i);
y = r*Math.sin(i);
return {cx:x+offset,cy:y+offset};
}
This is just a 5 min fiddling, so it might seem cheap. Of course, you can define better algorithms using transform functions.
jsfiddle here