I am using google charts to display a stacked column chart. I am using entity framework and linq queries to gather my data from the db.
The problems I am having is:
that it will not order the chart. I have ordered the chart but the x-axis remains un-ordered. Can this be done through the linq query or could I do it in the script?
Currently it only displays x-axis values for data that I have. Example is on the x-axis I have month number but it only displays marks for data I have eg. 1,4,5,6. Is there a way to include from 1-12 although there is no data for that particular month number?
Code:
#region Total Hours Per Month sick
var querythpshols = (from r in db.HolidayRequestForms
where (r.StartDate) >= dateAndTime
group r by r.MonthOfHoliday into g
select new { Value = g.Key, Count = g.Sum(h => h.HoursTaken)});
var resultthpshols = querythpshols.ToList();
var datachartthpshols = new object[resultthpshols.Count];
int G = 0;
foreach (var i in resultthpshols)
{
datachartthpshols[G] = new object[] { i.Value.ToString(), i.Count };
G++;
}
string datathpshols = JsonConvert.SerializeObject(datachartthpshols, Formatting.None);
ViewBag.datajthpshols = new HtmlString(datathpshols);
#endregion
#region Total Hours Per Month
var querythpshols1 = (from r in db.HolidayRequestForms
where (r.StartDate) <= dateAndTime
group r by r.MonthOfHoliday into g
select new { Value = g.Key, Count1 = g.Sum(r => r.HoursTaken) })
;
var resultthpshols1 = querythpshols1.ToList();
var datachartthpshols1 = new object[resultthpshols1.Count];
int P = 0;
foreach (var i in resultthpshols1)
{
datachartthpshols1[P] = new object[] { i.Value.ToString(), i.Count1 };
P++;
}
string datathpshols1 = JsonConvert.SerializeObject(datachartthpshols1, Formatting.None);
ViewBag.datajthpshols1 = new HtmlString(datathpshols1);
#endregion
Script:
#*TOTAL HOURS PER MONTH CHART*#
<scipt>
<script>
var datathpshols = '#ViewBag.datajthpshols';
var datassthpshols = JSON.parse(datathpshols);
var datathpshols1 = '#ViewBag.datajthpshols1';
var datassthpshols1 = JSON.parse(datathpshols1);
</script>
<script type="text/javascript">
// Load the Visualization API and the corechart package.
google.charts.load('current', { 'packages': ['corechart'] });
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(drawChartA);
// Callback that creates and populates a data table,
// instantiates the pie chart, passes in the data and
// draws it.
function drawChartA() {
// Create the data table.
var data1 = new google.visualization.DataTable();
data1.addColumn('string', 'Value');
data1.addColumn('number', 'Holiday Hours Booked');
data1.addRows(datassthpshols);
var data2 = new google.visualization.DataTable();
data2.addColumn('string', 'Value');
data2.addColumn('number', 'Holiday Hours Taken');
data2.addRows(datassthpshols1);
var joinedData = google.visualization.data.join(data1, data2, 'full', [[0, 0]], [1], [1]);
// Set chart options
var options = {
'title': 'Holiday Hours Taken Per Month',
'width': 600,
'height': 350,
'hAxis': { title: 'Month Number' },
'vAxis': { title: 'Holiday Hours Taken' },
'is3D': true,
'isStacked': true,
'legend': 'right'
};
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.ColumnChart(document.getElementById('chartTHPShols_div'));
chart.draw(joinedData, options);
}
</script>
1) Use data table method --> sort -- to order the x-axis.
joinedData.sort([{column: 0}]);
2) strings produce a discrete axis, and will only display the data available. numbers produce a continuous axis, and provide much more flexibility for the axis ticks. probably the most simplest solution would be to use a data view to convert the x-axis to numbers. (use the data view to draw the chart)
var joinedData = google.visualization.data.join(data1, data2, 'full', [[0, 0]], [1], [1]);
var dataView = new google.visualization.DataView(joinedData);
dataView.setColumns([{
calc: function (dt, row) {
return parseFloat(dt.getValue(row, 0));
},
label: joinedData.getColumnLabel(0),
type: 'number'
}, 1, 2]);
chart.draw(dataView, options);
Related
I am using this script to draw average spectral signature of all classes together and each class separately of a classified image by RF algorithm in GEE.
var bands = ['B1', 'B2', 'B3', 'B4','B5','B6','B7', 'B8', 'B8A', 'B9' ,'B11', 'B12','NDVI', 'EVI', 'GNDVI', 'NBR', 'NDII'];
var Training_Points = Water.merge(Residential).merge(Agricultural).merge(Arbusti).merge(BoschiMisti).merge(Latifoglie).merge(Conifere).merge(BareSoil);
var classes = ee.Image().byte().paint(Training_Points, "land_class").rename("land_class")
var stratified_points = classes.stratifiedSample({
numPoints: 50,
classBand: 'land_class',
scale: 10,
region: Training_Points,
geometries: false,
tileScale: 6
})
print(stratified_points, 'stratified_points')
//Create training data
var training_Stratified = RF_classified.select(bands).sampleRegions({
collection: stratified_points,
properties: ['land_class'],
scale:10,
tileScale:2
});
var bands = RF_classified.bandNames()
var numBands = bands.length()
var bandsWithClass = bands.add('land_class')
var classIndex = bandsWithClass.indexOf('land_class')
// Use .combine() to get a reducer capable of computing multiple stats on the input
var combinedReducer = ee.Reducer.mean().combine({
reducer2: ee.Reducer.stdDev(),
sharedInputs: true})
// Use .repeat() to get a reducer for each band and then use .group() to get stats by class
var repeatedReducer = combinedReducer.repeat(numBands).group(classIndex)
var stratified_points_Stats = training_Stratified.reduceColumns({
selectors: bands.add('land_class'),
reducer: repeatedReducer,
})
// Result is a dictionary, we do some post-processing to extract the results
var groups = ee.List(stratified_points_Stats.get('groups'))
var classNames = ee.List(['Water','Residential', 'Agricultural', 'Arbusti', 'BoschiMisti', 'Latifoglie','Conifere', 'BareSoil'])
var fc = ee.FeatureCollection(groups.map(function(item) {
// Extract the means
var values = ee.Dictionary(item).get('mean')
var groupNumber = ee.Dictionary(item).get('group')
var properties = ee.Dictionary.fromLists(bands, values)
var withClass = properties.set('class', classNames.get(groupNumber))
return ee.Feature(null, withClass)
}))
// Chart spectral signatures of training data
var options = {
title: 'Average Spectral Signatures',
hAxis: {title: 'Bands'},
vAxis: {title: 'Reflectance',
viewWindowMode:'explicit',
viewWindow: {
max:6000,
min:0
}},
lineWidth: 1,
pointSize: 4,
series: {
0: {color: '105af0'},
1: {color: 'dc350a'},
2: {color: 'caa712'},
3: {color: 'b9ffa4'},
4: {color: '369b47'},
5: {color: '21ff2d'},
6: {color: '275b25'},
7: {color: 'f7e084'},
}};
// Default band names don't sort propertly Instead, we can give a dictionary with labels for each band in the X-Axis
var bandDescriptions = {
'B2': 'B2/Blue',
'B3': 'B3/Green',
'B4': 'B4/Red',
'B5': 'B5/Red Edge 1',
'B6': 'B5/Red Edge 2',
'B7': 'B7/Red Edge 3',
'B8': 'B8/NIR',
'B8A': 'B8A/Red Edge 4',
'B11': 'B11/SWIR-1',
'B12': 'B12/SWIR-2'
}
// Create the chart and set options.
var chart = ui.Chart.feature.byProperty({
features: fc,
xProperties: bandDescriptions,
seriesProperty: 'class'
})
.setChartType('ScatterChart')
.setOptions(options);
print(chart)
var classChart = function(land_class, label, color) {
var options = {
title: 'Spectral Signatures for ' + label + ' Class',
hAxis: {title: 'Bands'},
vAxis: {title: 'Reflectance',
viewWindowMode:'explicit',
viewWindow: {
max:6000,
min:0
}},
lineWidth: 1,
pointSize: 4,
};
var fc = training_Stratified.filter(ee.Filter.eq('land_class', land_class))
var chart = ui.Chart.feature.byProperty({
features: fc,
xProperties: bandDescriptions,
})
.setChartType('ScatterChart')
.setOptions(options);
print(chart)
}
classChart(0, 'Water')
classChart(1, 'Residential')
classChart(2, 'Agricultural')
classChart(3, 'Arbusti')
classChart(4, 'BoschiMisti')
classChart(5, 'Latifoglie')
classChart(6, 'Conifere')
classChart(7, 'BareSoil')
I receive the error:
Error generating chart: Image.select: Pattern 'B1' did not match any
bands.
I do not understand where is the problem since I used the same script before to draw histogram of training data and it worked well.
I am following these tutorials on how to create an email and populate it with values from a Google Sheet.
https://developers.google.com/apps-script/articles/sending_emails
https://katydecorah.com/code/google-sheets-to-gmail/
It all works as expected, however I cannot apply any formatting to the email such as underline, bold or even tables. Any formatting I apply in the original Google Doc is removed when the email is generated.
EDIT:
Here is the Google-Doc used as the template
Dear {caregiver}
Herewith please find the Weekly Engagement Summary for {fname} {sname}
Week 1: {week_1_score}
Week 2: {week_2_score}
Thank You
And here is the Google-Sheet used with the values
fname|sname|caregiver|email|week_1_score|week_2_score|date drafted
bob|smith|parent1|user1#gmail.com|4|3
john|jones|parent2|user2#gmail.com|2|4
rob|brown|parent3|user3#live.com|3|5
And here is the code-behind script that glue them together and creates the email
// What is the Google Document ID for your email template?
var googleDocId = "<my id>";
// Which column has the email address? Enter the column row header exactly.
var emailField = 'email';
// What is the subject line?
var emailSubject = 'Weekly Engagement Indicator';
// Which column is the indicator for email drafted? Enter the column row header exactly.
var emailStatus = 'date drafted';
/* ----------------------------------- */
// Be careful editing beyond this line //
/* ----------------------------------- */
var sheet = SpreadsheetApp.getActiveSheet(); // Use data from the active sheet
function draftMyEmails() {
var emailTemplate = DocumentApp.openById(googleDocId).getText(); // Get your email template from Google Docs
var data = getCols(2, sheet.getLastRow() - 1);
var myVars = getCols(1, 1)[0];
var draftedRow = myVars.indexOf(emailStatus) + 1;
// Work through each data row in the spreadsheet
data.forEach(function(row, index){
// Build a configuration for each row
var config = createConfig(myVars, row);
// Prevent from drafing duplicates and from drafting emails without a recipient
if (config[emailStatus] === '' || config[emailStatus] !== '' && config[emailField]) {
// Replace template variables with the receipient's data
var emailBody = replaceTemplateVars(emailTemplate, config);
// Replace template variables in subject line
var emailSubjectUpdated = replaceTemplateVars(emailSubject, config);
// Create the email draft
GmailApp.createDraft(
config[emailField], // Recipient
emailSubjectUpdated, // Subject
emailBody // Body
);
sheet.getRange(2 + index, draftedRow).setValue(new Date()); // Update the last column
SpreadsheetApp.flush(); // Make sure the last cell is updated right away
}
});
}
function replaceTemplateVars(string, config) {
return string.replace(/{[^{}]+}/g, function(key){
return config[key.replace(/[{}]+/g, "")] || "";
});
}
function createConfig(myVars, row) {
return myVars.reduce(function(obj, myVar, index) {
obj[myVar] = row[index];
return obj;
}, {});
}
function getCols(startRow, numRows) {
var lastColumn = sheet.getLastColumn(); // Last column
var dataRange = sheet.getRange(startRow, 1, numRows, lastColumn) // Fetch the data range of the active sheet
return dataRange.getValues(); // Fetch values for each row in the range
}
Any help appreciated.
Thanks
I'm trying to create a custom world map where countries are merged into regions instead of having individual countries. Unfortunately for some reason something seems to get messed up with the winding order along the process.
As base data I'm using the natural earth 10m_admin_0_countries shape files available here. As criteria for merging countries I have a lookup map that looks like this:
const countryGroups = {
"EUR": ["ALA", "AUT", "BEL"...],
"AFR": ["AGO", "BDI", "BEN"...],
...
}
To merge the shapes I'm using topojson-client. Since I want to have a higher level of control than the CLI commands offer, I wrote a script. It goes through the lookup map and picks out all the topojson features that belong to a group and merges them into one shape and places the resulting merged features into a geojson frame:
const topojsonClient = require("topojson-client");
const topojsonServer = require("topojson-server");
const worldTopo = topojsonServer.topology({
countries: JSON.parse(fs.readFileSync("./world.geojson", "utf-8")),
});
const geoJson = {
type: "FeatureCollection",
features: Object.entries(countryGroups).map(([region, ids]) => {
const relevantCountries = worldTopo.objects.countries.geometries.filter(
(country, i) =>
ids.indexOf(country.properties.ISO_A3) >= 0
);
return {
type: "Feature",
properties: { region, countries: ids },
geometry: topojsonClient.merge(worldTopo, relevantCountries),
};
}),
};
So far everything works well (allegedly). When I try to visualise the map using github gist (or any other visualisation tool like vega lite) the shapes seem to be all messed up. I'm suspecting that I'm doing something wrong during the merging of the features but I can't figure out what it is.
When I try to do the same using the CLI it seems to work fine. But since I need more control over the merging, using just the CLI is not really an option.
The last feature, called "World", should contain all remaining countries, but instead, it contains all countries, period. You can see this in the following showcase.
var w = 900,
h = 300;
var projection = d3.geoMercator().translate([w / 2, h / 2]).scale(100);
var path = d3.geoPath().projection(projection);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select('svg')
.attr('width', w)
.attr('height', h);
var url = "https://gist.githubusercontent.com/Flave/832ebba5726aeca3518b1356d9d726cb/raw/5957dca433cbf50fe4dea0c3fa94bb4f91c754b7/world-regions-wrong.topojson";
d3.json(url)
.then(data => {
var geojson = topojson.feature(data, data.objects.regions);
geojson.features.forEach(f => {
console.log(f.properties.region, f.properties.countries);
});
svg.selectAll('path')
// Reverse because it's the last feature that is the problem
.data(geojson.features.reverse())
.enter()
.append('path')
.attr('d', path)
.attr('fill', d => color(d.properties.region))
.attr('stroke', d => color(d.properties.region))
.on('mouseenter', function() {
d3.select(this).style('fill-opacity', 1);
})
.on('mouseleave', function() {
d3.select(this).style('fill-opacity', null);
});
});
path {
fill-opacity: 0.3;
stroke-width: 2px;
stroke-opacity: 0.4;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.js"></script>
<script src="https://d3js.org/topojson.v3.js"></script>
<svg></svg>
To fix this, I'd make sure to always remove all assigned countries from the list. From your data, I can't see where "World" is defined, and if it contains all countries on earth, or if it's a wildcard assignment.
In any case, you should be able to fix it by removing all matches from worldTopo:
const topojsonClient = require("topojson-client");
const topojsonServer = require("topojson-server");
const worldTopo = topojsonServer.topology({
countries: JSON.parse(fs.readFileSync("./world.geojson", "utf-8")),
});
const geoJson = {
type: "FeatureCollection",
features: Object.entries(countryGroups).map(([region, ids]) => {
const relevantCountries = worldTopo.objects.countries.geometries.filter(
(country, i) =>
ids.indexOf(country.properties.ISO_A3) >= 0
);
relevantCountries.forEach(c => {
const index = worldTopo.indexOf(c);
if (index === -1) throw Error(`Expected to find country ${c.properties.ISO_A3} in worldTopo`);
worldTopo.splice(index, 1);
});
return {
type: "Feature",
properties: { region, countries: ids },
geometry: topojsonClient.merge(worldTopo, relevantCountries),
};
}),
};
I want to make stacked bar chart where each portion has a width that encodes one value (say "Change" in the data below) and a height that encodes another value ("Share")
In some ways this is like a histogram with different bin sizes. There are a few "histogram" questions but none seem to address this. Plot Histograms in Highcharts
So given data like this:
Category Share Price Change
Apples 14.35 0.1314192423
Horseradish 46.168 0.1761474117
Figs 2.871 0.018874249
Tomatoes 13.954 0.0106121298
Mangoes 7.264 0.1217297011
Raisins 5.738 0.0206787136
Eggplant 6.31 0.0110160732
Other produce 3.344 0.0945377722
I can make a stacked bar that captures the "share" column in widths:
And another that captures the "change" column in heights:
And I can use an image editor to combine those into this histogram-like beast:
Which really captures that horseradish is a huge deal. So my question is, can I do that within Highcharts?
You can realise that by using snippet.
(function (H) {
var seriesTypes = H.seriesTypes,
each = H.each,
extendClass = H.extendClass,
defaultPlotOptions = H.getOptions().plotOptions,
merge = H.merge;
defaultPlotOptions.marimekko = merge(defaultPlotOptions.column, {
pointPadding: 0,
groupPadding: 0
});
seriesTypes.marimekko = extendClass(seriesTypes.column, {
type: 'marimekko',
pointArrayMap: ['y', 'z'],
parallelArrays: ['x', 'y', 'z'],
processData: function () {
var series = this;
this.totalZ = 0;
this.relZ = [];
seriesTypes.column.prototype.processData.call(this);
each(this.zData, function (z, i) {
series.relZ[i] = series.totalZ;
series.totalZ += z;
});
},
translate: function () {
var series = this,
totalZ = series.totalZ,
xAxis = series.xAxis;
seriesTypes.column.prototype.translate.call(this);
// Distort the points to reflect z dimension
each(this.points, function (point, i) {
var shapeArgs = point.shapeArgs,
relZ = series.relZ[i];
shapeArgs.x *= (relZ / totalZ) / (shapeArgs.x / xAxis.len);
shapeArgs.width *= (point.z / totalZ) / (series.pointRange / series.xAxis.max);
});
}
});
}(Highcharts));
Example: http://jsfiddle.net/highcharts/75oucp3b/
Is there a more complete tutorial or guide to creating charts with dc.js than what is offered in their documentation? I'm trying to create a simple line chart with 2 stacked levels. I'm making use of the following csv:
I want the WasteDate to be on the x-axis and the WasteData to be on the y-axis. Further I want one layer to be of the WasteFunction Minimisation and the other to be of the WasteFunction Disposal. This should give me something like the following (very roughly):
Now, as I understand it, I need to create a dimension for the x-axis using crossfilter and then a filtered dimension for my 2 stacks.
The dimension for the x-axis will be the dates:
// dimension by month
var Date_dim = ndx.dimension(function (d) {
return d.WasteDate;
});
// Get min/max date for x-axis
var minDate = Date_dim.bottom(1)[0].WasteDate;
var maxDate = Date_dim.top(1)[0].WasteDate;
Then I need to create a dimension for the y-axis, then filter it for each of my stacks?
// WasteType dimension
var WasteFunction_dim = ndx.dimension(function (d) {
return d.WasteFunction;
});
// Minimisation Filter
var WasteFunction_Disposal = WasteFunction_dim.filter("Disposal");
// Disposal Filter
var WasteFunction_Minimisation = WasteFunction_dim.filter("Minimisation");
Then I should be able to use these to setup the chart:
moveChart
.renderArea(true)
.width(900)
.height(200)
.dimension(Date_dim)
.group(WasteFunction_Minimisation, 'Minimisation')
.stack(WasteFunction_Disposal, 'Disposal')
.x(d3.time.scale().domain([minDate, maxDate]));
Now, I can't get passed this error on the RenderAll() function:
The full code:
< script type = "text/javascript" >
$(document).ready(function() {
var moveChart = dc.lineChart('#monthly-move-chart');
d3.csv('minimisation-vs-disposal.csv', function(data) {
/* format the csv file a bit */
var dateFormat = d3.time.format('%d/%M/%Y');
var numberFormat = d3.format('.2f');
data.forEach(function(d) {
d.dd = dateFormat.parse(d.WasteDate);
d.WasteData = +d.WasteData // coerce to number
});
// Cross Filter instance
var ndx = crossfilter(data);
var all = ndx.groupAll();
// dimension by month
var Date_dim = ndx.dimension(function(d) {
return d.WasteDate;
});
// Get min/max date for x-axis
var minDate = Date_dim.bottom(1)[0].WasteDate;
var maxDate = Date_dim.top(1)[0].WasteDate;
// Waste Data dimension
var WasteData_dim = ndx.dimension(function(d) {
return d.WasteData;
});
// WasteType dimension
var WasteFunction_dim = ndx.dimension(function(d) {
return d.WasteFunction;
});
// Minimisation Filter
var WasteFunction_Disposal = WasteFunction_dim.filter("Disposal");
// Disposal Filter
var WasteFunction_Minimisation = WasteFunction_dim.filter("Minimisation");
moveChart
.renderArea(true)
.width(900)
.height(200)
.transitionDuration(1000)
.dimension(Date_dim)
.group(WasteFunction_Minimisation, 'Minimisation')
.stack(WasteFunction_Disposal, 'Disposal')
.x(d3.time.scale().domain([minDate, maxDate]));
dc.renderAll();
});
});
< /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/d3/3.4.11/d3.min.js"></script>
<div id="monthly-move-chart">
<strong>Waste minimisation chart</strong>
</div>
It's true, dc.js does not have much documentation. Someone could write a book but it hasn't happened. People mostly rely on examples to get started, and the annotated stock example is a good first read.
The biggest problem in your code is that those are not crossfilter groups. You really need to learn the crossfilter concepts to use dc.js effectively. Crossfilter has very strong documentation, but it's also very dense and you'll have to read it a few times.
Feel free to join us on the dc.js user group if you want to talk it through to get a better understanding. It does take a while to get the idea but it's worth it!