Canvas and PHP - Drawing connected line segments - php

I am trying to draw connected lines using canvas from data in a MySql table. The table (gene_dna_segments) contains the length of each line segment and the segment name.
The desired output is a continuous straight horizontal line comprised of each of the segments. Each segment also needs to have the segment name showing above the corresponding segment as shown in the image below:
+----------------------+--------------------+------------------+--------------------+
| gene_dna_segments_pk | gene_expression_fk | dna_segment_name | dna_segment_length |
+----------------------+--------------------+------------------+--------------------+
| 1 | 11 | Exon 1 | 50 |
| 2 | 11 | Intron 1 | 75 |
| 3 | 11 | Exon 2 | 20 |
| 4 | 11 | Intron 2 | 90 |
+----------------------+--------------------+------------------+--------------------+
Query (old fashioned no PDO...):
$query_dna = "SELECT * FROM gene_dna_segments WHERE gene_expression_fk = '11'";
$result_dna = mysql_query($query_dna, $connection) or die(mysql_error());
Display:
<canvas id="canvas" width="800" height="500"></canvas>
<script type="text/javascript">
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
<?php
while($row_dna = mysql_fetch_assoc($result_dna)) {
echo "context.beginPath();context.moveTo(100, 100);context.lineTo(" . $row_dna['dna_segment_length'] . ", 100);context.lineWidth = 12;context.strokeStyle = '#009543';context.stroke();context.font = 'bold 12pt Calibri';
context.fillStyle = '#009543';
context.fillText('" . $row_dna['dna_segment_name'] . "', 180, 90);";
}
?>
</script>
Now, the line segments and text as defined in the table are drawn OK, but on top of each other as the context.moveTo(100, 100) is the same for each row that is output in the while loop. How should this be handled so that the segments are drawn as a continuous line?
Possibly the easiest thing is to add a start point column to the table and calculate the start points for each segment based on the previous segment's length...I've opened a seperate question on that possibility Calculating new array values based on another numeric array
Note that 'normal' code to draw the lines is like the following code, note that the moveto parts start at the end of the previous line. I need to do the same in the loop...
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.beginPath();
context.moveTo(100, 100);
context.lineTo(150, 100);
context.lineWidth = 12;
context.strokeStyle = '#009543';
context.stroke();
context.font = 'bold 11pt Calibri';
context.fillStyle = '#009543';
context.fillText('Exon 1', 105, 90);
context.beginPath();
context.moveTo(150, 100);
context.lineTo(225, 100);
context.lineWidth = 12;
context.strokeStyle = '#e97300';
context.stroke();
context.font = 'bold 11pt Calibri';
context.fillStyle = '#e97300';
context.fillText('Intron 1', 165, 90);
context.beginPath();
context.moveTo(225, 100);
context.lineTo(275, 100);
context.lineWidth = 12;
context.strokeStyle = '#009543';
context.stroke();
context.font = 'bold 11pt Calibri';
context.fillStyle = '#009543';
context.fillText('Exon 2', 230, 90);
context.beginPath();
context.moveTo(275, 100);
context.lineTo(375, 100);
context.lineWidth = 12;
context.strokeStyle = '#e97300';
context.stroke();
context.font = 'bold 11pt Calibri';
context.fillStyle = '#e97300';
context.fillText('Intron 2', 300, 90);

[ Updated based on new info from questioner ]
What you're describing is a horizontally stacked bar-chart
Demo: http://jsfiddle.net/m1erickson/RzMWS/
Your barchart is fairly straightforward.
Small problem:
We need to display the segment_name label above each bar. In a perfect world, we could use the segment_length for the bar width. But in reality, sometimes the resulting bar width will be too narrow to fit the text label above.
Therefore, we must preprocess the data and determine an appropriate scale factor for the bars. By scaling the bars proportionally, each bar will remain proportional to their segment_length and will also be wide enough to fit the segment_name label.
Here is heavily commented code.
Of course, you will read from your database fetch instead of reading the test data in the rows[] array.
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
// get references to the canvas
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
ctx.font = 'bold 11pt Calibri';
// define the color of the bar for each segment_name
var colorCodes={
Exon1:"green",
Intron1:"orange",
Exon2:"green",
Intron2:"orange"
};
// create some test data
var rows=[];
rows.push({dna_segment_name:"Exon 1",dna_segment_length:50});
rows.push({dna_segment_name:"Intron 1",dna_segment_length:75});
rows.push({dna_segment_name:"Exon 2",dna_segment_length:20});
rows.push({dna_segment_name:"Intron 2",dna_segment_length:90});
// some variables
// segments[]: pre-processed data will be saved into segments[]
// scaleFactor: scales every bar if any text label fails to fit
// padding: allow minimum padding between text labels
var segments=[];
var scaleFactor=1;
var padding=5;
// pre-process
// Of course, you will be reading from your database Fetch
// instead of this test data in rows[]
for(var i=0;i<rows.length;i++){
var $row_dna=rows[i];
// make variables for the segment_name & segment_length
// being read from the data fetch
var name=$row_dna['dna_segment_name'];
var length=$row_dna['dna_segment_length'];
// lookup the color for this segment_name
var color=colorCodes[name.replace(/\s+/g, '')];
// rescale the bars if any text won't fit
var textWidth=ctx.measureText(name).width+padding;
var textRatio=textWidth/length;
if (textRatio>scaleFactor){ scaleFactor=textRatio; }
// save the pre-processed info in a javascript object
// for later processing
segments.push({
name:name,
length:length*scaleFactor,
color:color
});
}
// draw the stacked bar-chart
// based on the preprocessed JS objects in segments[]
var accumLength=0;
var y=100; // the Y-coordinate of the barchart
for(var i=0;i<segments.length;i++){
// load the object for the current bar
var segment=segments[i];
// set the bar color
ctx.fillStyle=segment.color;
// draw the bar
ctx.fillRect(accumLength,y,segment.length,12);
// draw the text label
ctx.fillText(segment.name,accumLength,y-10);
// accumulate where the next bar will begin
accumLength+=segment.length;
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=400 height=300></canvas>
</body>
</html>

Related

Limiting text with max height

I have a grid layout where I'd like to limit the height of the text, by cutting the text than adding more... link. I used the length to get where i have to cut the text. But when a post contains short lines and divided with enter than they count as a short text but their height is still high.
like this:
---------- ----------
|12345678| |12 |
|9more...| |34 |
| | |56 |
---------- |78 |
|9more...|
----------
So are there any solution to limit the height?
Ps. Sorry for my English.
Make a function which reads every post and if have more than Xpx of height, you will add that 'more...' anchor and also limit the height.
Example:
$('post').each(function(){
if($(this).height > 50){
$(this).css('max-height', '50px');
$(this).append('<a class="more">more....</a>');
}
});
Then you make another function that listens the class more and if the click its done you quit the max-height property and the more anchor.
Here is a good solution that will remove a single character from the content until it's height is as specified:
var maxHeight = 70;
var limiter = document.getElementById("limiter");
var height = limiter.clientHeight;
var content = limiter.innerText;
alert("Original Content:\n" + content);
var count = 0;
while (height > maxHeight && count < 100) {
count++;
content = content.substring(0, content.length - 1);
limiter.innerText = content + " more...";
height = limiter.clientHeight;
}
alert("Adjusted Content:\n" + content);
var html = limiter.innerHTML;
alert("Final HTML:\n" + html);
The count is there just to prevent getting stuck in an infinite loop, just in case. It is not necessary.
Fiddle: http://jsfiddle.net/38p8hhve/

PHP: d3.js script does not display anything

Problem: I've got the following code, that runs fine as a standalone .html file. Now, I wish to display this within a .php file. Inside the .php file, I've copy-pasted the same code.
There are some header/footer and other html in the main page where this .php file is to be displayed. However, when I run it But other than the line, "Testing", the chart does not display.
What additional changes should be done ?
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<div>
<h4>Testing</h4>
</div>
<!-- <script type="text/javascript" src="http://mbostock.github.com/d3/d3.js?2.4.5"></script> -->
<script src="d3.v3.js"></script>
<!-- Source for example located at: http://bl.ocks.org/1203641 -->
<style type="text/css">
.slice text {
font-size: 16pt;
font-family: Arial;
}
</style>
<script>
var canvasWidth = 500, //width
canvasHeight = 700, //height
outerRadius = 200, //radius
color = d3.scale.category20(); //builtin range of colors
var dataSet = [
{"legendLabel":"Your child's Percentile", "magnitude":90},
{"legendLabel":"Ahead of your child", "magnitude":10},
// {"legendLabel":"Three", "magnitude":50},
// {"legendLabel":"Four", "magnitude":16},
// {"legendLabel":"Five", "magnitude":50},
// {"legendLabel":"Six", "magnitude":8},
// {"legendLabel":"Seven", "magnitude":30}
];
var vis = d3.select("body")
.append("svg:svg") //create the SVG element inside the <body>
.data([dataSet]) //associate our data with the document
.attr("width", canvasWidth) //set the width of the canvas
.attr("height", canvasHeight) //set the height of the canvas
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + 1.5*outerRadius + "," + 1.5*outerRadius + ")") // relocate center of pie to 'outerRadius,outerRadius'
// This will create <path> elements for us using arc data...
var arc = d3.svg.arc()
.outerRadius(outerRadius);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) { return d.magnitude; }) // Binding each value to the pie
.sort( function(d) { return null; } );
// Select all <g> elements with class slice (there aren't any yet)
var arcs = vis.selectAll("g.slice")
// Associate the generated pie data (an array of arcs, each having startAngle,
// endAngle and value properties)
.data(pie)
// This will create <g> elements for every "extra" data element that should be associated
// with a selection. The result is creating a <g> for every object in the data array
.enter()
// Create a group to hold each slice (we will have a <path> and a <text>
// element associated with each slice)
.append("svg:g")
.attr("class", "slice"); //allow us to style things in the slices (like text)
arcs.append("svg:path")
//set the color for each slice to be chosen from the color function defined above
.attr("fill", function(d, i) { return color(i); } )
//this creates the actual SVG path using the associated data (pie) with the arc drawing function
.attr("d", arc);
// Add a legendLabel to each arc slice...
arcs.append("svg:text")
.attr("transform", function(d) { //set the label's origin to the center of the arc
//we have to make sure to set these before calling arc.centroid
d.outerRadius = outerRadius + 50; // Set Outer Coordinate
d.innerRadius = outerRadius + 45; // Set Inner Coordinate
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle") //center the text on it's origin
.style("fill", "Purple")
.style("font", "bold 12px Arial")
.text(function(d, i) { return dataSet[i].legendLabel; }); //get the label from our original data array
// Add a magnitude value to the larger arcs, translated to the arc centroid and rotated.
arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; }).append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
//.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")"; })
.attr("transform", function(d) { //set the label's origin to the center of the arc
//we have to make sure to set these before calling arc.centroid
d.outerRadius = outerRadius; // Set Outer Coordinate
d.innerRadius = outerRadius/2; // Set Inner Coordinate
return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")";
})
.style("fill", "White")
.style("font", "bold 12px Arial")
.text(function(d) { return d.data.magnitude; });
// Computes the angle of an arc, converting from radians to degrees.
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
</script>
</meta>
</head>
</html>
Works fine for me. The only difference is that I replaced your script tags with remote ones
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://dimplejs.org/dist/dimple.v2.1.0.min.js"></script>
If you are including this file from another PHP file, delete doctype, html, body and head tags - they can't be duplicate in generated file.
update: by the way, div tags don't belong to head section

Filling D3.js Pie Graph with SQL Query

I am currently using D3.js to make a pie graph. The data is stored in a MSSQL database, which is then converted to JSON using PHP. Here is my code that does that
<?php
// Server Name
$myServer = "SRVR";
// Database
$myDB = "TestDB";
// If using Windows Authentication, get rid of, "'UID'=>$myUser, 'PWD'=>$myPass, "
// Notice that the latest driver uses sqlsrv rather than mssql
$conn = sqlsrv_connect('Database'=>$myDB));
// Change TestDB.vwTestData to YOURDB.dbo.YOURTABLENAME
$sql = "SELECT col, SUM(num) AS 'value'
FROM db
GROUP BY col";
$result = array();
do {
while ($row = sqlsrv_fetch_array($data, SQLSRV_FETCH_ASSOC)){
$result[] = $row;
}
} while ( sqlsrv_next_result($data) );
// This will output in JSON format if you try to hit the page in a browser
echo json_encode($result);
sqlsrv_free_stmt($data);
sqlsrv_close($conn);
?>
This works fine. I've tested it, and it outputs JSON in something like this:
[{"col":null,"value":247.9042254},{"col":"value1","value":16.8151576061},{"col":"value2","value":235.4833175609},{"col":"value3","value":2316.072432028},{"col":"value4","value":8904.4001532729}]
How can I put this in the graph? Here is my .js code
(function() {
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return // Something goes here I assume });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.json("scripts/graphs/script.php", function(error, data) {
data.forEach(function(d) {
// Something needs to go here?
});
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.age); });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; });
});
})();
If someone could help me out, that would be great. Thanks!
I figured it out. I used this code and customized it for JSON
https://gist.github.com/enjalot/1203641
Here is what I got
(function() {
var w = 670, //width
h = 326, //height
r = 150, //radius
color = d3.scale.category20c(); //builtin range of colors
d3.json("script.php", function (data) {
var vis = d3.select("body")
.append("svg:svg") //create the SVG element inside the <body>
.data([data]) //associate our data with the document
.attr("width", w) //set the width and height of our visualization (these will be attributes of the <svg> tag
.attr("height", h)
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + r + "," + r + ")") //move the center of the pie chart from 0, 0 to radius, radius
var arc = d3.svg.arc() //this will create <path> elements for us using arc data
.outerRadius(r);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) { return d.value; }); //we must tell it out to access the value of each element in our data array
var arcs = vis.selectAll("g.slice") //this selects all <g> elements with class slice (there aren't any yet)
.data(pie) //associate the generated pie data (an array of arcs, each having startAngle, endAngle and value properties)
.enter() //this will create <g> elements for every "extra" data element that should be associated with a selection. The result is creating a <g> for every object in the data array
.append("svg:g") //create a group to hold each slice (we will have a <path> and a <text> element associated with each slice)
.attr("class", "slice"); //allow us to style things in the slices (like text)
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } ) //set the color for each slice to be chosen from the color function defined above
.attr("d", arc); //this creates the actual SVG path using the associated data (pie) with the arc drawing function
arcs.append("svg:text") //add a label to each slice
.attr("transform", function(d) { //set the label's origin to the center of the arc
//we have to make sure to set these before calling arc.centroid
d.innerRadius = 0;
d.outerRadius = r;
return "translate(" + arc.centroid(d) + ")"; //this gives us a pair of coordinates like [50, 50]
})
.attr("text-anchor", "middle") //center the text on it's origin
.text(function(d, i) { return data[i].col; }); //get the label from our original data array
})
})();
So what was wrong was that I wasn't connecting the key values from the JSON, to the variables in the JS. Here are the lines to change:
var vis = d3.select("<PUT DIV ID HERE>")
.value(function(d) { return d.<PUT NUMBER VALUE KEY NAME HERE>; });
.text(function(d, i) { return data[i].<PUT SLICE CATEGORY HERE>; });
I am not entirely certain what is not working about your code, but you could try something simple like:
d3.json("scripts/graphs/script.php", function(error, data) {
data.forEach(function(d) {
d.value = +d.value
});
var g...
Alternately, could you just call the php script and store the returned json object in a variable, then pass that variable to d3.json?

Dynamically set selection of jcrop (not fixed selection)

I am working with a jcrop now I want to do something like here
jQuery(function(){
jQuery(".image_container .a-center h2").html("Upload another picture")
var api;
jQuery('#cropbox').Jcrop({
bgOpacity: 0.5,
bgColor: 'black',
addClass: 'jcrop-dark',
aspectRatio: 320/180,
setSelect:[320,320,180,180],
onSelect: updateCoords,
onChange: updateCoords
},function(){
api = this;
api.setOptions({ bgFade: true });
api.ui.selection.addClass('jcrop-selection');
});
});
and it gives this kinda output on selection
and I want this kind of selection upon
what should I do to get max selection for width.
var ratio = 1.2222222;
var jcrop_api = $.Jcrop('#modal-file-crop-image');
jcrop_api.setOptions({ aspectRatio: ratio });
var dim = jcrop_api.getBounds();
dim should hold an array of the image size
so like [380,180]
i use the following to center the crop area based on aspect ratio
var $img = $("#modal-file-crop-image");
var w = $img.width();
var h = $img.height();
$img.css({"width": w, "height": h})
var ratio = 1.2222222;
var jcrop_api = $.Jcrop('#modal-file-crop-image');
jcrop_api.setOptions({ aspectRatio: ratio });
var dim = jcrop_api.getBounds();
var x = 0, y = 0, x_ = dim[0], y_ = dim[1];
var x_r = (x_ / ratio) - y_;
var y_r = (y_ / ratio) - x_;
if (x_r > 0) {
x = x_r / 2;
}
if (y_r > 0) {
y = y_r / 2;
}
jcrop_api.setSelect([x, y, dim[0], dim[1]]);
in your case you would probably just set y to 0 all the time
You can't.
It seems Like your main Image file is having bigger size the 320x180. You have select area of 320 x 180 which is smaller then your actual Image.
So Here what you can do is.
Calculate Area and set it up in your code. For this, calculate New Height / Width On the bases of Height and Width Of Image; taking other parameter as master. (e.g for your image in question, Master parameter is Width and you can find a new Height which is based on aspect ratio and master parameter.)
Setup area coordinates.
I think this will solve your issue.

How to display dynamically generated canvas image on other websites

Am working on a site which allows users to participate in polls and earn some points. I'd like them to be able to display their username + score + some other stuff on any website, for example their own blog, forum signatures, etc. as an image. Kindof like stackoverflow flair !
Ofcourse, since their scores and other data will keep changing, I'd like the image to be generated dynamically (I am using php). I have got to a point using canvas where the canvas-png image displays fine on my site, but if I try to use the page url as a src in an img tag, the image doesnot show up.
Below is the page which generates the canvas :
<html>
<head>
<script type="text/javascript" src="/jscripts/jquery.js"></script>
<script type="text/javascript">
$(document).ready(function()
{
var canvas = document.getElementById("c"),
context = canvas.getContext("2d");
context.fillStyle = "rgb(155, 155, 155)";
context.fillRect(0, 0, 250, 100);
context.stroke();
var imgObj = new Image();
imgObj.onload = function ()
{
// Draw the image on the canvas
context.drawImage(imgObj, 4, 8, 32, 32);
}
imgObj.src="<?=$iUser->avatar?>";
var username = "Username from php";
context.fillStyle = "rgb(0, 0, 0)";
context.font = "18px sans-serif";
context.fillText(username, 42, 20);
var score = "CGH Score : " + "146";
context.fillStyle = "rgb(0, 0, 0)";
context.font = "14px sans-serif";
context.fillText(score, 42, 40);
var img_data=canvas.toDataURL('image/png');
var img_element="<img src=\"" + img_data + "\" />";
$("#c").remove();
$("head").html("<meta http-equiv=\"content-type\" content=\"image/png\"/>");
$("body").html(img_element);
//document.body="<img src=\"" + img_data + "\" />";
//document.write(img_data);
//var output=img_data.replace(/^data:image\/(png|jpg);base64,/, "");
//$.post("/show_img/",{image_data:output});
//window.location = canvas.toDataURL('image/png');
});
</script>
</head>
<body>
<canvas id="c"></canvas>
</body>
</html>
If you load that into an image tag, the browser will NOT interpret/execute the JS. it'll try to figure what kind of binary image format (gif/jpg/png/etc...) the raw bytes of that page's source are, and fail. Img tags are not a way to load a remote page/code into a page.
For this to work, you'd need to have the users insert a snippet of JS which loads the script dynamically from your site.
e.g. instead of
<img src="http://yoursite.com/canvas.php" />
you'd have
<script src="http://yoursite.com/canvas.php" /></script>
Create these images with PHP instead.
http://php.net/manual/en/function.imagejpeg.php
Or put this page in an IFRAME if you insist to use Canvas.

Categories