How do I re-render a chart in d3.js using an on click event (dashboard
building)?
I'm developing an interactive school report card for a local nonprofit
using Bootstrap, PHP, MySQL and d3.js.
I have no problem getting the d3 charts to render (example) once the user
has selected a school (3 separate bar charts render onscreen when the user
submits the school selection form). I do this by executing queries,
encoding them as JSON in PHP/MySQL, then using the PHP include function
(on three separate occasions) to reference 3 separate JavaScript files
that contain the d3 code that renders the charts. The reference to the
data that the d3 is pulling in to populate the graphs is nested inside of
PHP tags inside each of these JavaScript files. Again, no issues here, the
charts render exactly where they're supposed to and they look good.
The issue is that I'd like users to be able to be able to change the
options on one of the charts and then have that chart re-render with the
new data when a button in that form is clicked.
One of the charts that is initially rendered is of 4th grade math test
score data from the state standardized test for the selected school. There
are other test subjects (English, Science and Social Studies) within the
4th grade that I'd like users to be able to view; additionally, the same 4
subjects are tested in grades 3 and grades 5 – 8. These options for other
tests and subjects appear in 2 select boxes that appear in a form located
in a div that appears just above the test score graph's div.
I use Ajax on change event handlers (raw JavaScript) to trigger a MySQL
query with the selected options (subject and grade level; school is pulled
from a hidden form element which contains the school id for the currently
selected school) every time the user's subject/grade level selection
changes. The JSON from the last query is then written into the value of
another element that appears in the same form as the subject/grade level
select boxes. PHP/html for the form (I'm only using one on change handler
in the code below):
echo "<form name='testselect' method=get>";
echo "<table>
<tbody>
<tr>
<td>";
echo "<select name='testtype' id='ttype'
onChange='javascript:ajaxFunction();'>";
$dboxquery1 = "SELECT distinct subject FROM leapfile2012
WHERE site_code='$_POST[school]' ORDER BY subject;";
$resultdbq1 = mysql_query($dboxquery1,$con) or die("SQL
Error 1: " . mysql_error());
while($dbqr1 = mysql_fetch_array($resultdbq1))
{
echo '<option value="'. $dbqr1['subject'] . '">' .
$testType[$dbqr1['subject']] . '</option>';
}
echo "</select>";
echo "</td>
<td>";
echo "<select name='testsub' id='tsub'>";
echo "<option value='ELA' selected='selected'>English</option>";
echo "</select>";
echo "</td>";
echo "<td>" . "<input type='hidden' name='hidden'
id='hidename' value='" . $_POST['school'] . "'></input></td>";
echo" <td>";
echo '<input type="button" id="data-submit" value="Get"
onClick="javascript:doofus();"></input>';
echo "</td>
<td>
<input type='hidden' name='hidden2' id='hidename2'
value=''></input>
</td>
</tr>
</tbody>
</table>";
echo '</form>';
An on click event (user clicks 'Get' in the subject/grade level form) then
triggers the d3 code that is supposed to repopulate the graph with the
test data for the newly selected grade and subject. The JSON for that
new/updated graph is pulled from the value of that second hidden element.
This is where everything breaks down, I get an error running the following
JavaScript/d3:
function doofus() {
var birdmanbirdman = document.getElementById('hidename2').value;
var whutitdo = birdmanbirdman.length;
var quote_be_gone = birdmanbirdman.substring(0,whutitdo);
//var i_hope_this_works = new Array(quote_be_gone);
//alert(i_hope_this_works);
//alert(typeof(i_hope_this_works));
//alert(typeof(birdmanbirdman));
var margin = {top: 35, right: 105, bottom: 30, left: 40},
width = 370 - margin.left - margin.right,
height = 289 - margin.top - margin.bottom;
var data = new Array(quote_be_gone);
//var dontwant = ["site_name","site_code","subject","testsub"];
var formatAsPercentage = d3.format(".1%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var color = d3.scale.ordinal()
.range(["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099"]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(1);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".0%"))
.tickSize(1);
var svg = d3.select("#area3").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
color.domain(d3.keys(data[0]).filter(function(key)
{ return key !== "year"; }));
data.forEach(function(d) {
var y0 = 0;
d.ages = color.domain().map(function(name) { return {name: name, y0:
y0, y1: y0 += +d[name]}; });
d.total = d.ages[d.ages.length - 1].y1;
});
x.domain(data.map(function(d) { return d.year; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.attr("font-size", "1.0em")
.style("text-anchor", "end")
/*.text("Population")*/;
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x(d.year) +
",0)"; });
state.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("stroke","black")
.attr("stroke-width",0.5)
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
state.selectAll("text.one")
.data(function(d) { return d.ages; })
.enter().append("text")
.attr("x", function(d,i) { return x.rangeBand()/2;})
.attr("y", function(d) { /*if ((y(d.y0) - y(d.y1))>=0.05) */ return
y(d.y1) + (y(d.y0) - y(d.y1))/2 + 6; })
.text(function (d) { if ((y(d.y0) - y(d.y1))>=0.05) return
formatAsPercentage(d.y1 - d.y0);})
.attr("fill", "white")
.attr("font-family", "sans-serif")
.attr("font-weight", "normal")
.attr("text-anchor", "middle")
.attr("font-size", "0.9em");
var legend = svg.selectAll(".legend")
.data(color.domain().slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 +
")"; });
legend.append("rect")
.attr("x", width + 6)
.attr("width", 12)
.attr("height", 12)
.attr("stroke","black")
.attr("stroke-width", 0.5)
.style("fill", color);
legend.append("text")
.attr("x", width + 100)
.attr("y", 5)
.attr("dy", ".35em")
.style("text-anchor", "end")
.style("font-size", "9px")
.text(function(d) { return d; });
svg.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "11px")
.style("font-weight", "normal")
/*.text(gtitle)*/;
}
When I inspect in Chrome, I get "Uncaught TypeError: Cannot read property
'length' of undefined" around this piece of code.
data.forEach(function(d) {
var y0 = 0;
d.ages = color.domain().map(function(name) { return {name: name, y0:
y0, y1: y0 += +d[name]}; });
d.total = d.ages[d.ages.length - 1].y1;
});
In the debugging process, I'm able to alert out the JSON that's supposed
to populate the graph. I'm then able to strip off opening and closing
quotes using native JavaScript functions (the native data type is string),
then successfully verify that the new data is indeed an object. The d3
code in the on click script doesn't seem to be recognizing the new
data/Object that I'm attempting to pass in.
I'm not sure what to do at this point. I haven't been able to find a
parallel example online.
Any help you could offer would be greatly appreciated. Forgive my
wordiness, I wanted to make sure you all understood my issue in detail. If
this is an elementary error, please go easy on me. I only started using
JavaScript in March, PHP in April, and d3 six weeks ago.
No comments:
Post a Comment