I am trying to use the dc.js library for charts. However, what I found was my charts were not loading properly. If I write my code as following, it would not show the charts or error messages. But if I include my script (except the first line of the script) in the ready function like below, the charts load well.
$(document).ready(function (){
d3.json('data/GDX_partial.json', function (data) {
//code goes here......
}
})
Any idea? I omitted the library references and data and I am sure the data is not the issue.
<script type="text/javascript">
observerPieChart = dc.pieChart("#observer-pie-chart");
d3.json('data/GDX_partial.json', function (data) {
var dateFormat = d3.time.format('%m/%d/%Y %I:%M:%S %p').parse;
var numberFormat = d3.format('.2f');
var features = data.features;
features.forEach(function (d) {
d.date = dateFormat(d.attributes.ADD_DATE);
d.newX = Math.round(d.geometry.x);
d.newY = Math.round(d.geometry.y);
});
var gdx = crossfilter(features);
var observerDim = gdx.dimension(function (d) {
return d.attributes.OBSERVER;
});
var obsvCount = observerDim.group().reduce(
function (p, v) {
return p + 1;
},
function (p, v) {
return p - 1;
},
function (p, v) {
return 0;
});
observerPieChart
.width(400)
.height(150)
.dimension(observerDim)
.group(obsvCount);
dc.renderAll();
});
</script>
</head>
<body>
<div class="container-fluid">
<div id="observer-pie-chart"></div>
</div>
</body>
Related
I have been building a reporting page in sharepoint with dc and crossfilter.
Right now on my page, I have 5 pie charts that render with no problem. However, when I tried to add a dc datatable to the page to show results of the charts as they are filtered, I get a javascript error on "resultsChart.render();"
Because no errors are given when I render each of the pie charts, I assume this to mean that something is off with the datatable object, or that I cannot call render() on that object (whatever it thinks it is).
Here are the relevant pieces of my code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.5.0/d3.min.js" type="text/javascript">
<script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.19/js/jquery.dataTables.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.min.js" type="text/javascript">
<script src="https://cdnjs.cloudflare.com/ajax/libs/dc/3.0.6/dc.min.js" type="text/javascript">
//connect to sharepoint site (change this URL to redirect)
var siteUrl = 'path';
var masterData = [];
//retrieve list data from above sharepoint site based on List Name
function retrieveListItems() {
var clientContext = new SP.ClientContext(siteUrl);
var oList = clientContext.get_web().get_lists().getByTitle('Upcoming');
var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml("<View><Query></Query></View>");
this.collListItem = oList.getItems(camlQuery);
clientContext.load(collListItem);
clientContext.executeQueryAsync(
Function.createDelegate(this, this.onQuerySucceeded),
Function.createDelegate(this, this.onQueryFailed)
);
}
//on query success
function onQuerySucceeded(sender, args) {
var listItemEnumerator = collListItem.getEnumerator();
while (listItemEnumerator.moveNext()) {
var data = {};
var oListItem = listItemEnumerator.get_current();
//set field keys on array objects
data.project = oListItem.get_item('Project_x0020_Type');
data.stoplight = oListItem.get_item('Stoplight');
data.appmgr = oListItem.get_item('AIT_x0020_App_x0020_Manager');
data.compdate = oListItem.get_item('Completion_x0020_Due_x0020_Date');
data.ait = oListItem.get_item('AIT_x0020_Number');
data.lob = oListItem.get_item('Business_x0020_Area');
data.sublob = oListItem.get_item('Business_x0020_Sub_x0020_Area');
masterData.push(data);
}//end while
var projectChart = dc.pieChart("#project", "project");
var stoplightChart = dc.pieChart("#stoplight", "stoplight");
var appmgrChart = dc.pieChart("#appmgr", "appmgr");
var lobChart = dc.pieChart("#lob", "lob");
var sublobChart = dc.pieChart("#sublob", "sublob");
var resultChart = dc.dataTable("#result_table", "result");
var ndx = crossfilter(masterData),
projectType = ndx.dimension(function(d) { return d.project;}),
stoplight = ndx.dimension(function(d) { return d.stoplight;}),
appMgr = ndx.dimension(function(d) { return d.appmgr;}),
compdate = ndx.dimension(function(d) { return d.compdate;}),
lob = ndx.dimension(function(d) { return d.lob;}),
sublob = ndx.dimension(function(d) { return d.sublob;})
projectTypeGroup = projectType.group();
stoplightGroup = stoplight.group(),
appMgrGroup = appMgr.group(),
compDateGroup = compdate.group(),
lobGroup = lob.group(),
sublobGroup = sublob.group();
projectChart
.dimension(projectType)
.group(projectTypeGroup)
.width(200)
.height(200)
.innerRadius(75)
stoplightChart
.dimension(stoplight)
.group(stoplightGroup)
.width(200)
.height(200)
.innerRadius(75)
appmgrChart
.dimension(appMgr)
.group(appMgrGroup)
.width(200)
.height(200)
.innerRadius(75)
lobChart
.dimension(lob)
.group(lobGroup)
.width(300)
.height(300)
.innerRadius(117)
sublobChart
.dimension(sublob)
.group(sublobGroup)
.width(200)
.height(200)
.innerRadius(75)
resultChart
.dimension(compdate)
.group(compDateGroup)
.columns([
function(d) { return d.ait},
function(d) { return d.project},
function(d) { return d.stoplight},
function(d) { return d.compdate}
])
.size(100);
projectChart.render();
stoplightChart.render();
appmgrChart.render();
lobChart.render();
sublobChart.render();
resultChart.render();
}
function onQueryFailed(sender, args) {
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
SP.SOD.executeOrDelayUntilScriptLoaded(retrieveListItems, 'sp.js');
</script>
Any and all help is extremely appreciated!
I figured it out. According to the dc.dataTable documentation, you cannot use a crossfilter group as the .group attribute on a datatable. Instead, you must explicitly use a function there.
So it should be
resultChart
.dimension(compdate)
.group(function(d) { return d.compdate;})
.columns([
function(d) { return d.ait},
function(d) { return d.project},
function(d) { return d.stoplight},
function(d) { return d.compdate}
])
.size(100);
Instead of
resultChart
.dimension(compdate)
.group(compDateGroup)
.columns([
function(d) { return d.ait},
function(d) { return d.project},
function(d) { return d.stoplight},
function(d) { return d.compdate}
])
.size(100);
I have difficulties to make Google spreadsheet and D3js' D3-request / D3-request > header works together via xhr requests.
I use the following JS :
d3.request(url)
.header("X-Requested-With", "XMLHttpRequest")
.mimeType("text/csv")
.get(function(error, data) {
if (error) throw error;
console.log('request: '+ data);
});
I get the following error:
XMLHttpRequest cannot load https://docs.google.com/spreadsheets/d/e/2PACX-1vSZyV9olwK_hx0BRFgLtTz5hs_Z…mROYhax3VD9AFXTvmcataf8LuSIpxGT2/pub?gid=1023695213&single=true&output=csv. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://fiddle.jshell.net' is therefore not allowed access.
Jsfiddle here
Any idea to bypass it ?
Using D3 only
d3.json(url, function (error, result) {
var data = [];
for (i = 0; i < result.feed.entry.length; i += 1) {
data.push({
"animal": result.feed.entry[i].gsx$animal.$t,
"population": result.feed.entry[i].gsx$population.$t
});
}
pie_chart(data, "#chart1");
});
Using jQuery
$.get(url, function (result) {
var data = [];
$(result.feed.entry).each(function () {
data.push({"animal": this.gsx$animal.$t, "population": this.gsx$population.$t});
});
pie_chart(data, "#chart2");
});
Using tabletop.
Tabletop.init({
key: key,
callback: function (data, tabletop) {
pie_chart(data, "#chart3");
},
simpleSheet: true
});
Below is just a simple example to get data from a Google spreadsheet and turn it into a D3 pie chart.
//draws a pie chart with D3
function pie_chart(data, id) {
var w = 400;
var h = 400;
var r = h / 2;
var color = d3.scale.category20c();
var vis = d3.select(id).append("svg:svg").data([data]).attr("width", w).attr("height", h).append("svg:g").attr("transform", "translate(" + r + "," + r + ")");
var pie = d3.layout.pie().value(function (d) {
return d.population;
});
var arc = d3.svg.arc().outerRadius(r);
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice");
arcs.append("svg:path")
.attr("fill", function (d, i) {
return color(i);
})
.attr("d", function (d) {
return arc(d);
});
arcs.append("svg:text").attr("transform", function (d) {
d.innerRadius = 0;
d.outerRadius = r;
return "translate(" + arc.centroid(d) + ")";
}).attr("text-anchor", "middle").text(function (d, i) {
return data[i].animal;
});
}
//the key of google spreadsheet
var key = "1moczdbrfFwCp0L4Ube1a4GevuDcj2XQmCnpjArF_UEY";
//the url for jQuery and D3
var url = "https://spreadsheets.google.com/feeds/list/" + key + "/od6/public/values?alt=json";
var i = 0;
//D3 only
d3.json(url, function (error, result) {
var data = [];
for (i = 0; i < result.feed.entry.length; i += 1) {
data.push({
"animal": result.feed.entry[i].gsx$animal.$t,
"population": result.feed.entry[i].gsx$population.$t
});
}
pie_chart(data, "#chart1");
});
//Jquery
$.get(url, function (result) {
var data = [];
$(result.feed.entry).each(function () {
data.push({"animal": this.gsx$animal.$t, "population": this.gsx$population.$t});
});
pie_chart(data, "#chart2");
});
//tabletop
Tabletop.init({
key: key,
callback: function (data, tabletop) {
pie_chart(data, "#chart3");
},
simpleSheet: true
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tabletop.js/1.4.3/tabletop.js"></script>
<div id="chart1" style="width: 480px; height: 400px;"><span>D3 only</span></div>
<hr>
<div id="chart2" style="width: 480px; height: 400px;"><span>Jquery</span></div>
<hr>
<div id="chart3" style="width: 480px; height: 400px;"><span>Tabletop</span></div>
I've spent a lot of time trying to figure out how to go inside anonymous functions in jasmine.
Sample of method:
numerateColumns: function (rows) {
rows.each(function () {
var $row = $(this);
$row.children().each(function (index) {
var $cell = $(this);
$cell.addClass('column-' + (index + 1));
});
});
}
Try to test with:
it("[TEST] Should call each method.", function () {
// setup
var rows = {
each: function () {
return {
children: function () {
return {
replaceWith: function () {
return null;
}
};
}
};
}
};
spyOn(rows, 'each').and.callThrough();
// method under test
module.numerateColumns(rows);
// expectations
expect(rows.each).toHaveBeenCalled();
});
But coverage test shows me that code of method is read only in first line (rows.each).
How to force it to read all the code inside (function() {}) ?
Why you want to test jQuery? It works perfectly, if not - some tests probably it will catch before new version publication.
function numerateColumns($rows) {
$rows.each(function() {
var $row = $(this);
$row.children().each(function(index) {
var $cell = $(this);
$cell.addClass('column-' + (index + 1));
});
});
}
describe('Iterate over columns`', function() {
var mockRows
beforeEach(function() {
mockRows = $('<div><div></div></div>')
})
it("calls .each() on passed jQuery collection", function() {
spyOn($, 'each').and.callThrough();
expect($.each).not.toHaveBeenCalled();
numerateColumns(mockRows);
expect($.each).toHaveBeenCalled();
});
it("adds CSS class to each child", function() {
var column = $(mockRows[0]).find('div');
expect(column.hasClass('column-1')).toBeFalsy()
numerateColumns(mockRows);
expect(column.hasClass('column-1')).toBeTruthy()
});
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
What you can test - it is extracted business logic that is independent to jQuery itself.
Okay, so I created a basic line chart where x = months and y = values as per the below CSV:
dates,purpose,num
01/04/2015,Commute,1
01/05/2015,Commute,15
01/06/2015,Commute,48
01/07/2015,Commute,4
01/08/2015,Commute,4
01/09/2015,Commute,52
01/10/2015,Commute,163
01/11/2015,Commute,222
01/12/2015,Commute,126
01/01/2016,Commute,174
01/02/2016,Commute,11
01/03/2016,Commute,15
01/04/2015,Walk,0
01/05/2015,Walk,600
01/06/2015,Walk,13
01/07/2015,Walk,1
01/08/2015,Walk,1
01/09/2015,Walk,14
01/10/2015,Walk,44
01/11/2015,Walk,60
01/12/2015,Walk,34
01/01/2016,Walk,47
01/02/2016,Walk,3
01/03/2016,Walk,900
HTML is as follows:
<head>
<title>dc.js - Line Chart Example</title>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="../css/dc.css"/>
</head>
<body>
<div id="test"></div>
<script type="text/javascript" src="../js/d3.js"></script>
<script type="text/javascript" src="../js/crossfilter.js"></script>
<script type="text/javascript" src="../js/dc.js"></script>
<script type="text/javascript">
var Chart5 = dc.lineChart("#test");
d3.csv("morley3.csv", function(data) {
var dateFormat = d3.time.format("%d/%m/%Y");
var numberFormat = d3.format(".2f");
data.forEach(function(d) {
d.dd = dateFormat.parse(d.dates);
d.month = d3.time.month(d.dd);
//d.day = d3.time.day(d.dd);
//d.year = d.dd.getFullYear();
//d.num = +d.num;
//d.purpose = d.purpose;
});
var facts = crossfilter(data);
var dateDimension = facts.dimension(function(d) {return d.month;});
var dateDimension2 = facts.dimension(function(d) { if (d.purpose == "Walk") {return d.month;}});
var numberByDate2 = dateDimension2.group().reduceSum(function(d) { return d.num; });
minDate = dateDimension2.bottom(1)[0];
maxDate = dateDimension2.top(1)[0];
Chart5
.renderArea(true)
.width(900)
.height(300)
.renderArea(false)
.brushOn(false)
.dimension(dateDimension2)
.group(numberByDate2)
.x(d3.time.scale().domain([minDate, maxDate]))
// .xUnits(d3.time.day)
renderHorizontalGridLines(true)
.elasticX(true)
.elasticY(true)
.legend(dc.legend().x(800).y(10).itemHeight(13).gap(5))
.valueAccessor(function (d) {return d.value;}) // What does this do?
.yAxisLabel("")
.xAxis();
dc.renderAll();
});
</script>
</body>
</html>
I've asked to show just "Walk" and the result is showing the months ok in the correct order.
The issue I'm having is that the first month is adding 835 to the result.
This is the sum of all "num" where "purpose" = "Commute".
See pic here: http://tinypic.com/r/qx9kih/8
Any ideas where I'm going wrong?
Dimension accessor functions must return naturally ordered values, and yours does not. If the values aren't naturally ordered, group calculations start randomly including incorrect records. Try changing your dimension to:
var dateDimension2 = facts.dimension(function(d) { return d.month; });
var numberByDate2 = dateDimension2.group().reduceSum(function(d) {
if (d.purpose == "Walk") { return d.num } else { return 0; };
});
In other words, do your filtering in the sum accessor function rather than the dimension accessor function.
Look at this code:
This creates four circles on the map in a same position and it creates addListener click event for each one too but I just can click on the last one. I want to fix it in a way that I can click on all of them to make setEditable(true) for each one.
<!DOCTYPE html>
<html>
<head>
<script
src="http://maps.googleapis.com/maps/api/js?key=AIzaSyDY0kkJiTPVd2U7aTOAwhc9ySH6oHxOIYM&sensor=false">
</script>
<script>
var selectedShape;
function clearSelection()
{
if(selectedShape)
{
selectedShape.setEditable(false);
selectedShape = null;
}
}
function setSelection(shape)
{
clearSelection();
selectedShape = shape;
shape.setEditable(true);
}
</script>
<script>
var amsterdam=new google.maps.LatLng(52.395715,4.888916);
function initialize()
{
var mapProp = {center:amsterdam, zoom:7, mapTypeId:google.maps.MapTypeId.ROADMAP};
var map = new google.maps.Map(document.getElementById("googleMap"),mapProp);
var myArray = [];
var myCity;
for(var i = 0; i < 4; i++)
{
myCity = new google.maps.Circle({
center:amsterdam,
radius:20000,
strokeColor:"#0000FF",
strokeOpacity:0.8,
strokeWeight:2,
fillColor:"#0000FF",
fillOpacity:0.4
});
myArray.push(myCity);
google.maps.event.addListener(myCity, 'click', function() {setSelection(myCity)});
myArray[i].setMap(map);
}
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="googleMap" style="width:500px;height:380px;"></div>
</body>
</html>
Use this instead of myCity :
google.maps.event.addListener(myCity, 'click', function() {
setSelection(this)
});
Using setSelection(myCity) will refer to the last myCity created.