I'm novice on JavaScript, but really amazed by amCharts.
So, I ask here, after search a lot on internet: is it possible (or someone can tell me how) make a table as a tooltip on a amCharts graph?
I mean: I have a trend graph, based on "date", if I click on a date of a day, view a popup with a table of the details of the day's data.
Searching and try a lot of code, and I reach a possible solution.
So, considering that there aren't similar questions here or developed like I ask, I post my solution to share it.
You can try clicking "RUN": When you clicking on a point of graph, an HTML Table was displayed filled by data (fake value in this example).
THIS IS THE SNIPPET:
function createTable(){
var table = "<table>";
table += "<thead>";
table += "<tr>";
table +="<th> Dealer </th>";
table +="<th> Percent </th>";
table +="<th> Proportional </th>";
table +="</tr>";
table +="</thead>";
table +="<tbody>";
for(var i=0;i<200;i++){
table += "<tr>";
table +="<td> New York </td>";
table +="<td> "+Math.random();+" </td>";
table +="<td> "+Math.random();+" </td>";
table +="</tr>";
};
table += "</tbody>";
table += "</table>";
return table;
};
var chartData = [{
date: new Date(2012, 0, 1),
distance: 227,
duration: 408},
{
date: new Date(2012, 0, 2),
distance: 371,
duration: 482},
{
date: new Date(2012, 0, 3),
distance: 433,
duration: 562},
{
date: new Date(2012, 0, 4),
distance: 345,
duration: 379},
{
date: new Date(2012, 0, 5),
distance: 480,
duration: 501},
{
date: new Date(2012, 0, 6),
distance: 386,
duration: 443},
{
date: new Date(2012, 0, 7),
distance: 348,
duration: 405},
{
date: new Date(2012, 0, 8),
distance: 238,
duration: 309},
{
date: new Date(2012, 0, 9),
distance: 218,
duration: 287},
{
date: new Date(2012, 0, 10),
distance: 349,
duration: 485},
{
date: new Date(2012, 0, 11),
distance: 603,
duration: 890},
{
date: new Date(2012, 0, 12),
distance: 534,
duration: 810}];
var chart;
AmCharts.ready(function() {
// SERIAL CHART
chart = new AmCharts.AmSerialChart();
chart.dataProvider = chartData;
chart.categoryField = "date";
chart.marginTop = 0;
chart.autoMarginOffset = 5;
chart.balloon.showBullet = false;
// AXES
// category axis
var categoryAxis = chart.categoryAxis;
categoryAxis.parseDates = true; // as our data is date-based, we set parseDates to true
categoryAxis.minPeriod = "DD"; // our data is daily, so we set minPeriod to DD
categoryAxis.autoGridCount = false;
categoryAxis.gridCount = 50;
categoryAxis.gridAlpha = 0;
categoryAxis.gridColor = "#000000";
categoryAxis.axisColor = "#555555";
// we want custom date formatting, so we change it in next line
categoryAxis.dateFormats = [{
period: 'DD',
format: 'DD'},
{
period: 'WW',
format: 'MMM DD'},
{
period: 'MM',
format: 'MMM'},
{
period: 'YYYY',
format: 'YYYY'}];
// as we have data of different units, we create two different value axes
// Duration value axis
var durationAxis = new AmCharts.ValueAxis();
durationAxis.title = "duration";
durationAxis.gridAlpha = 0.05;
durationAxis.axisAlpha = 0;
durationAxis.inside = true;
// the following line makes this value axis to convert values to duration
// it tells the axis what duration unit it should use. mm - minute, hh - hour...
durationAxis.duration = "mm";
durationAxis.durationUnits = {
DD: "d. ",
hh: "h ",
mm: "min",
ss: ""
};
chart.addValueAxis(durationAxis);
// Distance value axis
var distanceAxis = new AmCharts.ValueAxis();
distanceAxis.title = "distance";
distanceAxis.gridAlpha = 0;
distanceAxis.position = "right";
distanceAxis.inside = true;
distanceAxis.unit = "mi";
distanceAxis.axisAlpha = 0;
chart.addValueAxis(distanceAxis);
// GRAPHS
// duration graph
var durationGraph = new AmCharts.AmGraph();
durationGraph.title = "duration";
durationGraph.valueField = "duration";
durationGraph.type = "line";
durationGraph.valueAxis = durationAxis; // indicate which axis should be used
durationGraph.lineColor = "#CC0000";
durationGraph.balloonText = "[[value]]";
durationGraph.lineThickness = 1;
durationGraph.legendValueText = "[[value]]";
durationGraph.bullet = "square";
chart.addGraph(durationGraph);
// CURSOR
var chartCursor = new AmCharts.ChartCursor();
chartCursor.zoomable = false;
chartCursor.categoryBalloonDateFormat = "DD";
chartCursor.cursorAlpha = 0;
chart.addChartCursor(chartCursor);
// LEGEND
var legend = new AmCharts.AmLegend();
legend.bulletType = "round";
legend.equalWidths = false;
legend.valueWidth = 120;
legend.color = "#000000";
chart.addLegend(legend);
// SET UP CLICK EVENTS
// create prerequisite properties
AmCharts.clickTimeout = 0; // this will hold setTimeout reference
AmCharts.lastClick = 0; // last click timestamp
AmCharts.doubleClickDuration = 300; // distance between clicks in ms - if it's less than that - it's a doubleckick
// let's define functions to actually do something on clicks/doubleclicks
// you will want to replace the insides of these with your own code
AmCharts.doSingleClick = function (event) {
//var div = document.getElementById("databody");
var table=createTable();
document.getElementById("databody").innerHTML=table;
//div.innerHTML = "Ciao<br />" + div.innerHTML;
}
/*AmCharts.doDoubleClick = function (event) {
var div = document.getElementById("events");
div.innerHTML = "Double Click<br />" + div.innerHTML;
}*/
// create click handler
AmCharts.myClickHandler = function (event) {
var ts = (new Date()).getTime();
if ((ts - AmCharts.lastClick) < AmCharts.doubleClickDuration) {
// it's double click!
// let's clear the timeout so the "click" event does not fire
if (AmCharts.clickTimeout) {
clearTimeout(AmCharts.clickTimeout);
}
// reset last click
AmCharts.lastClick = 0;
// now let's do whatever we want to do on double-click
AmCharts.doDoubleClick(event);
}
else {
// single click!
// let's delay it to see if a second click will come through
AmCharts.clickTimeout = setTimeout(function () {
// let's do whatever we want to do on single click
AmCharts.doSingleClick(event);
}, AmCharts.doubleClickDuration);
}
AmCharts.lastClick = ts;
}
// add handler to the chart
chart.addListener("clickGraphItem", AmCharts.myClickHandler);
// WRITE
chart.write("chartdiv");
});
body {
font-family: Verdana;
font-size: 12px;
padding: 10px;
}
#databody, #databody th, #databody td {
border: 1px solid #ccc;
padding: 10px;
}
#databody th {
font-weight: bold;
background-color: #eee;
}
div.box{
width:360px !important; width /**/:200px;
height:190px !important; height /**/: 200px;
padding: 4px;
border:1px solid #EEE; border-right:0 solid;
background:url(gradient.png) repeat-x fixed top left;
overflow:auto
}
}
<script src="http://www.amcharts.com/lib/amcharts.js" type="text/javascript"></script>
<div id="chartdiv" style="height: 362px;"></div>
<div class="box" id="databody">
</div>
Related
So I have this quite CPU-consuming app: https://codepen.io/team/amcharts/pen/47c41af971fe467b8b41f29be7ed1880
It's a Canvas on which things are drawn (a lot).
HTML:
<script src="https://cdn.amcharts.com/lib/5/index.js"></script>
<script src="https://cdn.amcharts.com/lib/5/xy.js"></script>
<script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
<div id="chartdiv" style="width:100%; height:400px"></div>
JavaScript:
/**
* ---------------------------------------
* This demo was created using amCharts 5.
*
* For more information visit:
* https://www.amcharts.com/
*
* Documentation is available at:
* https://www.amcharts.com/docs/v5/
* ---------------------------------------
*/
// Create root element
// https://www.amcharts.com/docs/v5/getting-started/#Root_element
var root = am5.Root.new("chartdiv");
// Set themes
// https://www.amcharts.com/docs/v5/concepts/themes/
root.setThemes([
am5themes_Animated.new(root)
]);
// Generate random data
var value = 100;
function generateChartData() {
var chartData = [];
var firstDate = new Date();
firstDate.setDate(firstDate.getDate() - 1000);
firstDate.setHours(0, 0, 0, 0);
for (var i = 0; i < 50; i++) {
var newDate = new Date(firstDate);
newDate.setSeconds(newDate.getSeconds() + i);
value += (Math.random() < 0.5 ? 1 : -1) * Math.random() * 10;
chartData.push({
date: newDate.getTime(),
value: value
});
}
return chartData;
}
var data = generateChartData();
// Create chart
// https://www.amcharts.com/docs/v5/charts/xy-chart/
var chart = root.container.children.push(am5xy.XYChart.new(root, {
focusable: true,
panX: true,
panY: true,
wheelX: "panX",
wheelY: "zoomX",
scrollbarX:am5.Scrollbar.new(root, {orientation:"horizontal"})
}));
var easing = am5.ease.linear;
// Create axes
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
var xAxis = chart.xAxes.push(am5xy.DateAxis.new(root, {
maxDeviation: 0.5,
groupData: false,
extraMax:0.1, // this adds some space in front
extraMin:-0.1, // this removes some space form th beginning so that the line would not be cut off
baseInterval: {
timeUnit: "second",
count: 1
},
renderer: am5xy.AxisRendererX.new(root, {
minGridDistance: 50
}),
tooltip: am5.Tooltip.new(root, {})
}));
var yAxis = chart.yAxes.push(am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {})
}));
// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
var series = chart.series.push(am5xy.ColumnSeries.new(root, {
name: "Series 1",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value",
valueXField: "date",
tooltip: am5.Tooltip.new(root, {
pointerOrientation: "horizontal",
labelText: "{valueY}"
})
}));
series.data.setAll(data);
// Add cursor
// https://www.amcharts.com/docs/v5/charts/xy-chart/cursor/
var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
xAxis: xAxis
}));
cursor.lineY.set("visible", false);
// Update data every second
setInterval(function () {
addData();
}, 100)
function addData() {
var lastDataItem = series.dataItems[series.dataItems.length - 1];
var lastValue = lastDataItem.get("valueY");
var newValue = value + ((Math.random() < 0.5 ? 1 : -1) * Math.random() * 5);
var lastDate = new Date(lastDataItem.get("valueX"));
var time = am5.time.add(new Date(lastDate), "second", 1).getTime();
series.data.removeIndex(0);
series.data.push({
date: time,
value: newValue
})
var newDataItem = series.dataItems[series.dataItems.length - 1];
newDataItem.animate({
key: "valueYWorking",
to: newValue,
from: lastValue,
duration: 600,
easing: easing
});
var animation = newDataItem.animate({
key: "locationX",
to: 0.5,
from: -0.5,
duration: 600
});
if (animation) {
var tooltip = xAxis.get("tooltip");
if (tooltip && !tooltip.isHidden()) {
animation.events.on("stopped", function () {
xAxis.updateTooltip();
})
}
}
}
setTimeout(function(){
xAxis.zoom(0.5, 1)
}, 1500)
After some time of running it, all Chromium browsers crash. Even though Dev tools does not show any memory leak - number of listeners, nodes and heap size remains the same (the heap might increase a bit initially but then stabilizes). But the task manager shows memory growing. Important thing - the browser window must be focused. The strange thing is that if I zoom-out the chart using scrollbar or resize window or close and open the tab, the memory could drop to a normal level. This thing happens both with dev tools opened and closed.
This does not happen on Firefox, so I believe it's some browser issue. Appreciate for any insights.
So we found out that this is indeed a Chromium issue, calling setTransform on a context (if nothing else is done) results to leak (which is not visible when profiling) and a crash. We reported it as a bug and hopefully it will be fixed. Meanwhile we are working on a workaround to avoid this situation.
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
var context = canvas.getContext('2d');
function loop() {
requestAnimationFrame(() => {
loop();
for (var j = 0; j < 10000; ++j) {
context.setTransform(0.5, 1, 1, 0.5, 1, 1);
}
});
}
loop();
I want to add markers on the Google Timeline chart as shown here Timeline Chart with Markers
I am currently following the solution given here: Google Charts Add Layer On Top Of Timeline. But, here there needs to be a timeline element only then can the marker be present over it. But, I want a way that a marker can be added without having any timeline data at that position in the row. Is there a built in feature for adding markers in Google Timelines, or a custom way which does not require adding a dummy timeline.
there are no built-in features for adding markers.
and since the answer you reference is a custom solution,
we can modify the solution to fit our needs.
we don't necessarily need a timeline element in order to place a marker.
but we do need data, in order to draw the timeline,
on which to place the markers.
out of the box, the timeline will limit the x-axis to the range of dates found in the data.
but we can set a custom x-axis range, to make it larger,
and allow more room for markers, where there are no timeline elements.
hAxis: {
minValue: dateRangeStart,
maxValue: dateRangeEnd,
}
see following working snippet...
google.charts.load('current', {
packages:['timeline']
}).then(function () {
var container = document.getElementById('timeline');
var chart = new google.visualization.Timeline(container);
var dataTable = new google.visualization.DataTable();
dataTable.addColumn({type: 'string', id: 'Row'});
dataTable.addColumn({type: 'string', id: 'Bar'});
dataTable.addColumn({type: 'date', id: 'Start'});
dataTable.addColumn({type: 'date', id: 'End'});
var currentYear = (new Date()).getFullYear();
dataTable.addRows([
['Row 1', 'A-1', new Date(currentYear, 0, 1), new Date(currentYear, 2, 31)],
['Row 1', 'A-2', new Date(currentYear, 3, 1), new Date(currentYear, 5, 30)],
['Row 2', 'B-1', new Date(currentYear, 6, 1), new Date(currentYear, 8, 31)],
['Row 2', 'B-2', new Date(currentYear, 9, 1), new Date(currentYear, 11, 31)]
]);
var dataTableGroup = google.visualization.data.group(dataTable, [0]);
var dateRangeStart = new Date(currentYear - 1, 0, 1);
var dateRangeEnd = new Date(currentYear + 1, 11, 31);
var rowHeight = 44;
var options = {
height: (dataTableGroup.getNumberOfRows() * rowHeight) + rowHeight,
hAxis: {
minValue: dateRangeStart,
maxValue: dateRangeEnd,
}
};
function drawChart() {
chart.draw(dataTable, options);
}
// add custom marker
function addMarkers(events) {
var baseline;
var baselineBounds;
var chartElements;
var labelFound;
var labelText;
var marker;
var markerLabel;
var markerSpan;
var rowLabel;
var svg;
var svgNS;
var timeline;
var timelineUnit;
var timelineWidth;
var timespan;
var xCoord;
var yCoord;
// initialize chart elements
baseline = null;
svg = null;
svgNS = null;
timeline = null;
chartElements = container.getElementsByTagName('svg');
if (chartElements.length > 0) {
svg = chartElements[0];
svgNS = svg.namespaceURI;
}
chartElements = container.getElementsByTagName('rect');
if (chartElements.length > 0) {
timeline = chartElements[0];
}
chartElements = container.getElementsByTagName('path');
if (chartElements.length > 0) {
baseline = chartElements[0];
}
if ((svg === null) || (timeline === null) || (baseline === null)) {
return;
}
timelineWidth = parseFloat(timeline.getAttribute('width'));
baselineBounds = baseline.getBBox();
timespan = dateRangeEnd.getTime() - dateRangeStart.getTime();
timelineUnit = (timelineWidth - baselineBounds.x) / timespan;
// add events
events.forEach(function (event) {
// find row label
rowLabel = dataTable.getValue(event.row, 0);
chartElements = container.getElementsByTagName('text');
if (chartElements.length > 0) {
Array.prototype.forEach.call(chartElements, function(label) {
if (label.textContent.indexOf('…') > -1) {
labelText = label.textContent.replace('…', '');
} else {
labelText = label.textContent;
}
if (rowLabel.indexOf(labelText) > -1) {
markerLabel = label.cloneNode(true);
}
});
}
// calculate placement
markerSpan = event.date.getTime() - dateRangeStart.getTime();
// add label
markerLabel.setAttribute('text-anchor', 'start');
markerLabel.setAttribute('fill', event.color);
markerLabel.setAttribute('x', (baselineBounds.x + (timelineUnit * markerSpan) + 6));
markerLabel.textContent = event.name;
svg.appendChild(markerLabel);
// add marker
xCoord = (baselineBounds.x + (timelineUnit * markerSpan) - 4);
yCoord = parseFloat(markerLabel.getAttribute('y'));
switch (event.type) {
case 'triangle':
marker = document.createElementNS(svgNS, 'polygon');
marker.setAttribute('fill', 'transparent');
marker.setAttribute('stroke', event.color);
marker.setAttribute('stroke-width', '3');
marker.setAttribute('points', xCoord + ',' + (yCoord - 10) + ' ' + (xCoord - 5) + ',' + yCoord + ' ' + (xCoord + 5) + ',' + yCoord);
svg.appendChild(marker);
break;
case 'circle':
marker = document.createElementNS(svgNS, 'circle');
marker.setAttribute('cx', xCoord);
marker.setAttribute('cy', yCoord - 5);
marker.setAttribute('r', '6');
marker.setAttribute('stroke', event.color);
marker.setAttribute('stroke-width', '3');
marker.setAttribute('fill', 'transparent');
svg.appendChild(marker);
break;
}
});
}
google.visualization.events.addListener(chart, 'ready', function () {
addMarkers([
{row: 0, date: new Date(currentYear - 1, 1, 11), name: 'Event 1', type: 'triangle', color: 'red'},
{row: 1, date: new Date(currentYear + 1, 5, 23), name: 'Event 2', type: 'circle', color: 'purple'},
{row: 3, date: new Date(currentYear + 1, 8, 2), name: 'Event 3', type: 'triangle', color: 'magenta'}
]);
});
window.addEventListener('resize', drawChart, false);
drawChart();
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="timeline"></div>
I'm looking to clip the cloud mask function (top of my code) to the AOI. For some reason, my other layers (dNDVI and RGB) work for the AOI, but my cloud mask doesn't apply to it. I've tried putting in the ee.Image.clip function but I couldn't get it to work.
I would love to have some insight as to why it won't apply for this specific layer. Very new to coding!
// --------- Cloud mask: -----------------
var mask_all = function(image) {
var hollstein_mask = cld.hollstein_S2(['shadow', 'cloud', 'cirrus'])(image);
return hollstein_mask;
};
var color = "#98ff00";
var AOI = ee.Geometry.Polygon(
[[[140.88237590701584,-37.896469860299604],
[140.96546001346115,-37.896469860299604],
[140.96546001346115,-37.83819826191272],
[140.88237590701584,-37.83819826191272],
[140.88237590701584,-37.896469860299604]]], null, false);
/* [[[140.5710269570096,-37.669974265519755],
[140.64037815329866,-37.669974265519755],
[140.64037815329866,-37.60037237657578],
[140.5710269570096,-37.60037237657578],
[140.5710269570096,-37.669974265519755] */
//load images for composite
var Sen2mosaic = ee.ImageCollection("COPERNICUS/S2_SR");
ee.Geometry.Polygon(140.88237590701584,-37.896469860299604);
var getQABits = function(image, start, end, newName) {
// Compute the bits we need to extract.
var pattern = 0;
for (var i = start; i <= end; i++) {
pattern += Math.pow(2, i);
}
// Return a single band image of the extracted QA bits, giving the band
// a new name.
return image.select([0], [newName])
.bitwiseAnd(pattern)
.rightShift(start);
};
// A function to mask out cloudy pixels.
var cloud_shadows = function(image) {
// Select the QA band.
var QA = image.select(['B8', 'B11', 'B4']);
// Get the internal_cloud_algorithm_flag bit.
return getQABits(QA, 3,3, 'cloud_shadows').eq(0);
// Return an image masking out cloudy areas.
};
// A function to mask out cloudy pixels.
var clouds = function(image) {
// Select the QA band.
var QA = image.select(['B8', 'B11', 'B4']);
// Get the internal_cloud_algorithm_flag bit.
return getQABits(QA, 5,5, 'Cloud').eq(0);
// Return an image masking out cloudy areas.
};
var maskClouds = function(image) {
var cs = cloud_shadows(image);
var c = clouds(image);
image = image.updateMask(cs);
return image.updateMask(c);
};
// --------- Input: ------------------------------------
var startDate = ee.Date('2018-12-31') //2016-09-15');
var endDate = ee.Date('2021-06-01');
var AOI =
ee.Geometry.Polygon(
[[[140.88237590701584,-37.896469860299604],
[140.96546001346115,-37.896469860299604],
[140.96546001346115,-37.83819826191272],
[140.88237590701584,-37.83819826191272],
[140.88237590701584,-37.896469860299604]]], null, false);
/* [[[140.5710269570096,-37.669974265519755],
[140.64037815329866,-37.669974265519755],
[140.64037815329866,-37.60037237657578],
[140.5710269570096,-37.60037237657578],
[140.5710269570096,-37.669974265519755] */
//Map.addLayer(AOI, {}, 'AOI', true);
Map.centerObject(AOI, 13);
var imageStartDate1 = startDate.advance(-30,"day");
var imageStartDate2 = startDate.advance(30,"day");
var imageEndDate1 = endDate.advance(-30,"day");
var imageEndDate2 = endDate.advance(30,"day");
var imagery = ee.ImageCollection("COPERNICUS/S2_SR");
//S2-SR: COPERNICUS/S2_SR //LANDSAT/LC08/C01/T1_SR
var Sen2 = ee.ImageCollection(imagery
// Filter by dates.
.filterDate(imageStartDate1, imageStartDate2)
// Filter by location.
.filterBounds(AOI));
var Sen2end = ee.ImageCollection(imagery
// Filter by dates.
.filterDate(imageEndDate1, imageEndDate2)
// Filter by location.
.filterBounds(AOI));
var Sen2mosaic = Sen2.mosaic().clip(AOI);
var Sen2mosaicEnd = Sen2end.mosaic().clip(AOI);
//print("Sen2mosaic", Sen2mosaic);
var composite_free = Sen2end.map(maskClouds);
var visParams = {bands:['B8', 'B11', 'B4'], min:0, max:5000};
Map.addLayer(composite_free.first(), visParams, 'cloud mask');
// Create the NDVI and NDWI spectral indices.
var ndvi = Sen2mosaic.normalizedDifference(['B8', 'B4']);
var ndwi = Sen2mosaic.normalizedDifference(['B3', 'B8']);
var ndviEnd = Sen2mosaicEnd.normalizedDifference(['B8', 'B4']);
var ndwiEnd = Sen2mosaicEnd.normalizedDifference(['B3', 'B8']);
// Create some binary images from thresholds on the indices.
// This threshold is designed to detect bare land.
var bare1 = ndvi.lt(0.2).and(ndwi.lt(0.3));
var bare1End = ndviEnd.lt(0.2).and(ndwiEnd.lt(0.3));
// This detects bare land with lower sensitivity. It also detects shadows.
var bare2 = ndvi.lt(0.2).and(ndwi.lt(0.8));
var bare2End = ndviEnd.lt(0.2).and(ndwiEnd.lt(0.8));
// Define visualization parameters for the spectral indices.
var ndviViz = {min: -1, max: 1, palette: ['FF0000', '00FF00']};
var ndwiViz = {min: 0.5, max: 1, palette: ['00FFFF', '0000FF']};
// Mask and mosaic visualization images. The last layer is on top.
var thematic = ee.ImageCollection([
// NDWI > 0.5 is water. Visualize it with a blue palette.
ndwi.updateMask(ndwi.gte(0.5)).visualize(ndwiViz),
// NDVI > 0.2 is vegetation. Visualize it with a green palette.
ndvi.updateMask(ndvi.gte(0.2)).visualize(ndviViz),
// Visualize bare areas with shadow (bare2 but not bare1) as gray.
bare2.updateMask(bare2.and(bare1.not())).visualize({palette: ['AAAAAA']}),
// Visualize the other bare areas as white.
bare1.updateMask(bare1).visualize({palette: ['FFFFFF']}),
]).mosaic();
var thematicEnd = ee.ImageCollection([
ndwiEnd.updateMask(ndwiEnd.gte(0.5)).visualize(ndwiViz),
ndviEnd.updateMask(ndviEnd.gte(0.2)).visualize(ndviViz),
bare2End.updateMask(bare2End.and(bare1.not())).visualize({palette: ['AAAAAA']}),
bare1End.updateMask(bare1End).visualize({palette: ['FFFFFF']}),
]).mosaic();
//Map.addLayer(thematic, {}, 'thematic', false);
//Map.addLayer(thematicEnd, {}, 'thematic end', false);
//Map.addLayer(ndvi, {}, 'NDVI', false);
//Map.addLayer(ndviEnd, {}, 'NDVI end', false);
var Band_R = Sen2mosaic.expression('RED / count', {'RED': Sen2mosaic.select('B4'), count : Sen2.size()});
var Band_G = Sen2mosaic.expression('GREEN / count', {'GREEN': Sen2mosaic.select('B3'), count : Sen2.size()});
var Band_B = Sen2mosaic.expression('BLUE / count', {'BLUE': Sen2mosaic.select('B2'), count : Sen2.size()});
var Band_Rend = Sen2mosaicEnd.expression('RED / count', {'RED': Sen2mosaicEnd.select('B4'), count : Sen2.size()});
var Band_Gend = Sen2mosaicEnd.expression('GREEN / count', {'GREEN': Sen2mosaicEnd.select('B3'), count : Sen2.size()});
var Band_Bend = Sen2mosaicEnd.expression('BLUE / count', {'BLUE': Sen2mosaicEnd.select('B2'), count : Sen2.size()});
var image_RGB = Band_R.addBands(Band_G).addBands(Band_B);
var image_RGBend = Band_Rend.addBands(Band_Gend).addBands(Band_Bend);
var image_viz_params = {
//'bands': ['B5', 'B4', 'B3'],
'min': 2,
'max': 240,
gamma: 1.5,
//'gamma': [0.95, 1.1, 1]
};
Map.addLayer(image_RGB, image_viz_params, 'RGB 12/2018', false);
Map.addLayer(image_RGBend, image_viz_params, 'RGB 12/2020', false);
var dNDVI = ndviEnd.subtract(ndvi);
// Scale product to USGS standards
var dNDVIscaled = dNDVI.multiply(1000);
// Add the difference image to the console on the right
print("Difference Normalized Difference Vegetation Index: ", dNDVI);
//--------------------------- NDVI Product - classified -------------------------------
// Define an SLD style of discrete intervals to apply to the image.
var sld_intervals =
'<RasterSymbolizer>' +
'<ColorMap type="intervals" extended="false" >' +
'<ColorMapEntry color="#ff1b1b" quantity="-250" label="-250" />' +
'<ColorMapEntry color="#ffa81b" quantity="-100" label="-100" />' +
'<ColorMapEntry color="#f5ff1b" quantity="250" label="250" />' +
'<ColorMapEntry color="#1bff2f" quantity="500" label="500" />' +
'<ColorMapEntry color="#099b16" quantity="1000" label="1000" />' +
'</ColorMap>' +
'</RasterSymbolizer>';
Map.addLayer(dNDVIscaled.sldStyle(sld_intervals), {}, 'dNDVI classified');
//==========================================================================================
// ADD A LEGEND
// set position of panel
var legend = ui.Panel({
style: {
position: 'bottom-left',
padding: '8px 15px'
}});
// Create legend title
var legendTitle = ui.Label({
value: 'dNDVI Classes',
style: {fontWeight: 'bold',
fontSize: '18px',
margin: '0 0 4px 0',
padding: '0'
}});
// Add the title to the panel
legend.add(legendTitle);
// Creates and styles 1 row of the legend.
var makeRow = function(color, name) {
// Create the label that is actually the colored box.
var colorBox = ui.Label({
style: {
backgroundColor: '#' + color,
// Use padding to give the box height and width.
padding: '8px',
margin: '0 0 4px 0'
}});
// Create the label filled with the description text.
var description = ui.Label({
value: name,
style: {margin: '0 0 4px 6px'}
});
// return the panel
return ui.Panel({
widgets: [colorBox, description],
layout: ui.Panel.Layout.Flow('horizontal')
})};
// Palette with the colors
var palette =['ff1b1b', 'ffa81b', 'f5ff1b', '1bff2f', '099b16', 'ffffff'];
// name of the legend
var names = ['Forest Loss, High Probability', 'Forest Loss', 'Unchanged', 'Forest Gain, Low', 'Forest Gain, High Probability'];
// Add color and and names
for (var i = 0; i < 5; i++) {
legend.add(makeRow(palette[i], names[i]));
}
// add legend to map (alternatively you can also print the legend to the console)
Map.add(legend);
// Export a cloud-optimized GeoTIFF.
Export.image.toDrive({
image: dNDVIscaled,
description: 'imageToCOGeoTiffExample',
scale: 10,
region: AOI,
fileFormat: 'GeoTIFF',
formatOptions: {
cloudOptimized: true
}
});
https://code.earthengine.google.com/586a1c0df85e74d4df8871618a965f6a
I used Amcharts to show a chart, and I make a div for it in a Bootstrap component. Followed is my html & js code:
<fieldset>
<legend>Chart</legend>
<div class="row">
<form class="form-horizontal mb-sm line-height-3">
<div class="col-sm-3 col-md-3">
<div class="form-group">
<label for="submitStartTime" class="col-sm-3 control-label">Start Time</label>
<div class="col-sm-6">
<label id="submitStartTime"></label>
</div>
</div>
<div id="latencyChart" class="col-sm-9 col-md-9" style="height: 400px;"></div>
</form>
</div>
</fieldset>
js:
drawChart: function(graphType){
var that = this;
that.generateChartData();
this.chart.dataProvider = this.chartData;
this.chart.categoryField = "date";
this.chart.balloon.bulletSize = 5;
// listen for "dataUpdated" event (fired when chart is rendered) and call zoomChart method when it happens
this.chart.addListener("dataUpdated", that.zoomChart());
// AXES
// category
var categoryAxis = this.chart.categoryAxis;
categoryAxis.parseDates = true; // as our data is date-based, we set parseDates to true
categoryAxis.minPeriod = "DD"; // our data is daily, so we set minPeriod to DD
categoryAxis.dashLength = 1;
categoryAxis.minorGridEnabled = true;
categoryAxis.twoLineMode = true;
categoryAxis.dateFormats = [{
period: 'fff',
format: 'JJ:NN:SS'
}, {
period: 'ss',
format: 'JJ:NN:SS'
}, {
period: 'mm',
format: 'JJ:NN'
}, {
period: 'hh',
format: 'JJ:NN'
}, {
period: 'DD',
format: 'DD'
}, {
period: 'WW',
format: 'DD'
}, {
period: 'MM',
format: 'MMM'
}, {
period: 'YYYY',
format: 'YYYY'
}];
categoryAxis.axisColor = "#DADADA";
// value
var valueAxis = new AmCharts.ValueAxis();
valueAxis.axisAlpha = 0;
valueAxis.dashLength = 1;
this.chart.addValueAxis(valueAxis);
// GRAPH
var graph = new AmCharts.AmGraph();
graph.title = "red line";
graph.valueField = "visits";
graph.bullet = "round";
graph.bulletBorderColor = "#FFFFFF";
graph.bulletBorderThickness = 2;
graph.bulletBorderAlpha = 1;
graph.lineThickness = 2;
graph.lineColor = "#5fb503";
graph.negativeLineColor = "#efcc26";
graph.hideBulletsCount = 50; // this makes the chart to hide bullets when there are more than 50 series in selection
this.chart.addGraph(graph);
// SCROLLBAR
var chartScrollbar = new AmCharts.ChartScrollbar();
this.chart.addChartScrollbar(chartScrollbar);
this.chart.creditsPosition = "bottom-right";
// WRITE
this.chart.write("latencyChart");
this.chart.write("chartdiv");
},
// this method is called when chart is first inited as we listen for "dataUpdated" event
zoomChart: function() {
// different zoom methods can be used - zoomToIndexes, zoomToDates, zoomToCategoryValues
this.chart.zoomToIndexes(this.chartData.length - 40, this.chartData.length - 1);
},
generateChartData: function(){
var firstDate = new Date();
firstDate.setDate(firstDate.getDate() - 10);
for (var i = 0; i < 10; i++) {
// we create date objects here. In your data, you can have date strings
// and then set format of your dates using chart.dataDateFormat property,
// however when possible, use date objects, as this will speed up chart rendering.
var newDate = new Date(firstDate);
newDate.setDate(newDate.getDate() + i);
var visits = Math.round(Math.random() * 40) - 20;
this.chartData.push({
date: newDate,
visits: visits
});
}
},
this.chart is a global variable in my js code, and it's already initialized. And the chart is shown in my page like below.
But Chrome console give some error info like below.
I debugged for some time and find out that the error happens in this.chart.write("latencyChart"). The error is cannot read property 'call' of undefined. Then I created a new div in this page called "chartdiv" to know whether the program can still work, and I added this.chart.write("chartdiv"); after the original one. and I found that there is no chart in "chartdiv" and the error goes on.
The issue is how you're assinging the zoomChart method to your dataUpdated event - that.zoomChart() is calling the zoomChart method, rather than assigning it to the event. You need to create a function that calls zoomChart so that it has access to your object's chart property during the event, i.e.
this.chart.addListener("dataUpdated", function() {
that.zoomChart()
});
Here's a fiddle with this fix.
Alternatively, you can use the dataUpdated event's argument object to access the chart object that way instead of fiddling with this/that/etc, for example:
this.chart.addListener("dataUpdated", that.zoomChart);
// ...
zoomChart: function(e) {
// different zoom methods can be used - zoomToIndexes, zoomToDates, zoomToCategoryValues
e.chart.zoomToIndexes(e.chart.dataProvider.length - 40, e.chart.dataProvider.length - 1);
},
Fiddle
I am using a linechart at graphael. My datapoints are dates,which are not recognisable by the graphael. So I have represented every date, using 1,2,3 ....
The fact is that I need to display dates at my chart, as the x axis labels. How I can do that? I tried the label property, but it does not working.
My code is shown below:
var lines = r.linechart(30, 30, 600, 440,[[1,2,3,4,5]],[[100,150,130,85,100]], {axisxstep : 20,nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true }).hoverColumn(function () {
this.tags = r.set();
for (var i = 0, ii = this.y.length; i < ii; i++) {
this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
}
}, function () {
this.tags && this.tags.remove();
});
Take a look at this fiddle. You have to change the attribute of each label (date for us) inside chartobject:
var lineChart = r.linechart(0, 0, 200, 250, xValues, yValues1, {
smooth: true,
colors: ['#F00', '#0F0', '#FF0'],
symbol: 'circle',
axis: '0 0 1 1'
});
for (var i = 0; i < lineChart.axis[0].text.items.length; i++) {
var label = lineChart.axis[0].text.items[i];
var originalDate = new Date(parseInt(label.attr('text'), 10));
var newText = originalDate.getDate() + "/" + (originalDate.getMonth() + 1) + "/" + (originalDate.getFullYear());
label.rotate(60);
label.attr({
'text': newText
});
}
Also, the data for graphael can only be numerical type to plot the graph, we have to create a Date object for each label in x like this:
var xValues = [
new Date("01/05/2014"),
new Date("01/06/2014"),
new Date("01/07/2014"),
new Date("01/08/2014"),
new Date("01/09/2014"),
new Date("01/10/2014"),
new Date("01/11/2014"),
new Date("01/12/2014"),
new Date("01/13/2014")
];