amcharts angular zoomlevelchanged event - amcharts

When I click on a country the zoomlevelchanged event is fired a hundred times! I expect that it fires a maximum of 2 times.
Is that intentional or did I do something wrong?
I wanted to provide a codepen sample post however it will break if I insert the code below:
chart.events.on("zoomlevelchanged", function(e) {
console.log(e);
});
The event shouldn't be triggered that often in my opinion because it will take some resource if it needs to compute s.th. based on the zoom level.
Is it possible to use another event to react when somebody zoomed in the map? It shouldn't be bound by the direction of the zoom like up or down and also it shouldn't be different between mouse and smartphone inputs.
The complete code:
import { Component, NgZone, AfterViewInit, OnDestroy } from "#angular/core";
import * as am4core from "#amcharts/amcharts4/core";
import * as am4maps from "#amcharts/amcharts4/maps";
import am4geodata_worldLow from "#amcharts/amcharts4-geodata/worldLow";
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit, OnDestroy {
private chart: am4maps.MapChart;
constructor(private zone: NgZone) { }
ngAfterViewInit() {
this.zone.runOutsideAngular(() => {
// create map instance
let chart = am4core.create("mapdiv", am4maps.MapChart);
// set map definition
chart.geodata = am4geodata_worldLow;
// set projection
chart.projection = new am4maps.projections.Miller();
// Create map polygon series
var polygonSeries = chart.series.push(new am4maps.MapPolygonSeries());
// Configure series
let polygonTemplate = this.configureTemplate(polygonSeries.mapPolygons.template);
// Make map load polygon (like country names) from GeoJSON
polygonSeries.useGeodata = true;
// Use data instead for additional information
polygonSeries.data = [{
"id": "US",
"name": "United States",
"value": 100,
"fill": am4core.color("#F05C5C")
}, {
"id": "FR",
"name": "France",
"value": 50,
"fill": am4core.color("#5C5CFF")
}];
polygonTemplate.propertyFields.fill = "fill";
// exclude antarktika
polygonSeries.exclude = ["AQ"];
// include germany only
// polygonSeries.include = ["DE"];
// ImageSeries (for adding visual objects like markers)
let imageSeries = chart.series.push(new am4maps.MapImageSeries());
// Basic Circle Image
let imageSeriesTemplate = imageSeries.mapImages.template;
let circle = imageSeriesTemplate.createChild(am4core.Circle);
circle.radius = 4;
circle.fill = am4core.color("#B27799");
circle.stroke = am4core.color("#FFFFFF");
circle.strokeWidth = 2;
circle.nonScaling = true;
circle.tooltipText = "{title}";
// Binding Marker Properties to Data
imageSeriesTemplate.propertyFields.latitude = "latitude";
imageSeriesTemplate.propertyFields.longitude = "longitude";
// Adding 3 Markers to the Map
imageSeries.data = [{
"latitude": 48.856614,
"longitude": 2.352222,
"title": "Paris"
}, {
"latitude": 40.712775,
"longitude": -74.005973,
"title": "New York"
}, {
"latitude": 49.282729,
"longitude": -123.120738,
"title": "Vancouver"
}];
// Adding Zoom Controls
// chart.zoomControl = new am4maps.ZoomControl();
// Heat map based on the value property in data
// polygonSeries.heatRules.push({
// "property": "fill",
// "target": polygonSeries.mapPolygons.template,
// "min": am4core.color("#ffffff"),
// "max": am4core.color("#AAAA00")
// });
chart.events.on("zoomlevelchanged", function(e) {
console.log(e);
});
// chart code goes here
this.chart = chart;
});
}
configureTemplate(polygonTemplate) {
// Create ToolTip
polygonTemplate.tooltipText = "{name}";
// Fill Template Color
polygonTemplate.fill = am4core.color("#BADA55");
// Create hover state
let hoverState = polygonTemplate.states.create("hover");
// set alternative Fill Color when hovering
hoverState.properties.fill = am4core.color("#367B25");
// show additional data in tooltip, akquired from polygonSeries.data
polygonTemplate.tooltipText = "{name}: {value}";
// Zooming to map area on click
polygonTemplate.events.on("hit", function(ev){
ev.target.series.chart.zoomToMapObject(ev.target)
});
return polygonTemplate;
}
ngOnDestroy() {
this.zone.runOutsideAngular(() => {
if (this.chart) {
this.chart.dispose();
}
});
}
}

Related

amCharts - Create multiple series from JSON

Is it possible to create multiple series from the json outlined below? If not, what would be the most efficient way to do so?
[
{
"Label": "Series 1",
"Std_Dev": 12.3003,
"Return": 3.1513
},
{
"Label": "Series 2",
"Std_Dev": 10.0244,
"Return": 3.1808
},
{
"Label": "Series 3",
"Std_Dev": 7.7506,
"Return": 3.1057
},
{
"Label": "Series 4",
"Std_Dev": 5.5022,
"Return": 2.9264
}
]
My example code is below, I'm trying to produce a scatter chart where each series will be labelled on the legend. The chart data is the json.
am4core.ready(function () {
am4core.useTheme(am4themes_animated);
// Create chart instance
var chart = am4core.create("chart-container", am4charts.XYChart);
// Create axes
var valueAxisX = chart.xAxes.push(new am4charts.ValueAxis());
valueAxisX.title.text = 'Standard Deviation';
// Create value axis
var valueAxisY = chart.yAxes.push(new am4charts.ValueAxis());
valueAxisY.title.text = 'Return';
chart.data = myJSON;
// Function to create series
function createSeries(r, sd, name, fill) {
var lineSeries = chart.series.push(new am4charts.LineSeries());
lineSeries.dataFields.valueY = Return;
lineSeries.dataFields.valueX = Std_Dev;
lineSeries.strokeOpacity = 0;
lineSeries.name = name;
lineSeries.stroke = fill;
lineSeries.fill = am4core.color(fill).lighten(0.0);
var bullet = lineSeries.bullets.push(new am4charts.CircleBullet());
}
// Create Series
createSeries(what, to, put, here);
});
}
I managed to achieve this by:
chart.data = data;
function createSeries(data) {
var lineSeries = chart.series.push(new am4charts.LineSeries());
lineSeries.data = data;
lineSeries.dataFields.valueY = "Return";
lineSeries.dataFields.valueX = "Std_Dev";
lineSeries.valueY = 1;
lineSeries.valueX = 2;
lineSeries.strokeOpacity = 0;
lineSeries.name = lineSeries.data[0]['Label'];
var bullet = lineSeries.bullets.push(new am4charts.CircleBullet());
};
// Create Series by looping through object
chart.data.forEach(function (obj) {
createSeries([obj]);
});

Need to display crossfilter data array values in separate lines instead of comma separated values in a single line

My data looks like below:
var data = [{
"Id": "1",
"startTime": 1554750660000,
"endTime": 1554751320000,
"Alerts": [
"Alert1","Alert2"
],
"Apps": [
"App1","App2"
]
}]
I have to display above data using crossfilter-data-table.
Currently, I am able to display the data like below:
StartTime EndTime Apps Alerts
04/08/2019 12
12:42:00 PM 12:49:00 PM App1,App2 Alert1,Alert2
Instead, I want to show it in below format.
Changes are: the array values of columns Apps, Alerts are not displayed as comma separated values. Instead they are shown in a separate line one after the other.
StartTime EndTime Apps Alerts
04/08/2019 12
12:42:00 PM 12:49:00 PM App1 Alert1
App2 Alert2
My current code looks like below:
import React from "react";
import * as dc from "dc";
import "dc/dc.css";
import * as d3 from "d3";
import { ChartTemplate } from "./chartTemplate";
import { css } from "glamor";
const tableFunc = (divRef, ndx) => {
const summaryTable = dc.dataTable(divRef);
const dimension = ndx.dimension(d => d.StartTime);
summaryTable
.dimension(dimension)
.showGroups(true)
.size(10000)
.group(d => {
return d.hour;
})
.columns([
{
label: "Start Time",
format: function(d) {
return d. startTime;
}
},
{
label: "End Time",
format: function(d) {
return d. endTime;
}
},
{
label: "Apps",
format: function(d) {
return d.Apps;
}
},
{
label: "Alerts",
format: function(d) {
return d.Alerts;
}
}
])
.sortBy(function(d) {
return d.startTime;
})
.order(d3.descending)
.on("renderlet", function(table) {
table.selectAll(".dc-table-group").classed("info", true);
});
return summaryTable;
};
const style = css({
"& tr": {
"&:hover": {
background: "#dddd"
}
},
"& td": {
textAlign: "left",
borderTop: "1px solid #ddd"
}
});
export const DataTable = props => (
<ChartTemplate
chartFunction={tableFunc}
styles={style}
title="Summary"
/>
);
What change i should do to display details in required format as above.Please help I'm really new to react, crossfilter and d3.
Assuming it's okay to display all the array values in one cell, I think the easiest way to get what you want would be simply to format the cell using <br> - the line break element.
E.g.
{
label: "Apps",
format: function(d) {
return d.Apps.join('<br>');
}
},
It would be a lot more difficult to create extra rows, since data table rows usually correspond 1:1 with rows of the data set.
Adding links
#zubug55 commented
In my real data, alerts look like:
"Alerts": [ "abc#link1","def#link2" ]
How do I break each abc#link1 on # and make abc a link to the url link1?
It's probably easiest to generate the anchor elements in HTML at the same time, like
return d.Alerts.map(function(a) {
var parts = a.split('#');
return '' + parts[0] + '';
}).join('<br>');

Programatically placing marker in drilldown map amchart

I have a map which is a drilldown map. It goes from continents view to a country view.
My goal is to place markers dynamically based on the selected country (after the drilldown).
Here in this example I want to place a marker in Berlin (Germany) however this marker doesn't get created.
Example: https://codepen.io/ms92o/pen/gjMPEJ?editors=1111
var map = AmCharts.makeChart("chartdiv", {
"type": "map",
"theme": "light",
"areasSettings": {
"autoZoom": true,
"rollOverOutlineColor": "#9a7bca",
"selectedColor": "#9a7bca",
"color": "#a791b4",
"rollOverColor": "#9a7bca"
},
"zoomControl": {
"buttonFillColor": "#a6bd7f",
"buttonRollOverColor": "#9a7bca"
},
"dataProvider": continentsDataProvider,
"objectList": {
"container": "listdiv"
},
"listeners": [{
"event": "clickMapObject",
"method": function (event) {
console.log(event);
// TODO: how to create some markers here based on the selected country?
let rep = { title: 'Berin', latitude: '52.520', longitude: '13.409779' };
rep.svgPath = targetSVG;
rep.zoomLevel = 3;
rep.scale = 1.2;
rep.label = rep.title;
map.dataProvider.images.push(rep);
}
}]
});
You need to call the map's validateNow()/validateData() methods whenever you update the map with new areas/markers or changes to its properties. The caveat of these calls is that they reset the map's zoom position unless you modify the dataProvider's zoom properties (zoomLevel, zoomLatitude and zoomLongitude), which also affects the home button unless you reset them after the fact.
Here's a solution that adds the marker while making sure the zoom level sticks and fixes the home button afterward:
"listeners": [{
"event": "clickMapObject",
"method": function (event) {
let rep = { title: 'Berin', latitude: '52.520', longitude: '13.409779' };
rep.svgPath = targetSVG;
rep.zoomLevel = 3;
rep.scale = 1.2;
rep.label = rep.title;
map.dataProvider.images.push(rep);
//delay the update so that the click+zoom action still occurs before
//adding the marker
setTimeout(function() {
//preserve current zoom level on update
map.dataProvider.zoomLevel = map.zoomLevel();
map.dataProvider.zoomLatitude = map.zoomLatitude();
map.dataProvider.zoomLongitude = map.zoomLongitude();
map.validateNow(); //add marker
//reset the zoom values so that the home button zooms
//completely out when clicked
map.dataProvider.zoomLevel = 0;
map.dataProvider.zoomLatitude = undefined;
map.dataProvider.zoomLongitude = undefined;
}, (map.zoomDuration + .5) * 1000);
}
}]
Updated codepen

Showing sum of multiple values with amCharts

I would like to show sum of multiple values as one chart output with amCharts. I am using dataLoader with JSON to get the data. I know I have to create a function for but I couldn't understand how to get the data from the dataLoader to calculate
{
"balloonText": "[[title]] of [[valueAxis]]:[[value]]",
"lineThickness": 3,
"id": "sumValue",
"title": "sum Value",
"valueField": (function() {
var sumValues = "calculation";
return sumValues
}
this attempt is probably not correct but this is how I started
{
"balloonText": "[[title]] of [[valueAxis]]:[[value]]",
"lineThickness": 3,
"id": "LoadigTime",
"title": "Loadig Time",
"valueField": (function() {
var sumValues = (HomePageLoad + LoginToParametersLoad + ParametersLoad + AlarmsLoad + SwitchSideLoad + LoginToAdminLoad + AdminLoad) / 7;
return sumValues
})
}
valueField cannot be a function, only a string reference to a field in your data.
If the chart is meant to be displaying the sum of all of those fields in your data as a chart, simply add logic to your postProcess callback to create a new dataset containing your sums, e.g.
postProcess: function(data) {
var newData = [];
data.forEach(function(dataItem) {
var item = {
YOUR_CATEGORY_FIELD: dataItem.YOUR_CATEGORY_FIELD, //replace with your category field name
sum: 0
};
//loop through your item's keys and sum everything up, filtering out
//your category property
item.sum = Object.keys(dataItem).reduce(function(sum, key) {
if (key !== "YOUR_CATEGORY_FIELD") {
sum += dataItem[key]
}
return sum;
}, 0);
newData.push(item);
});
return newData;
},
// ...
graphs: [{
valueField: "sum",
// other props here
}]

dimple.js filter data by legend gives error

I'm using a dimple bar chart legend to filter the chart's data as given in this fiddle https://jsfiddle.net/fbpnzy9u/.
var svg = dimple.newSvg("#chartContainer", 590, 400);
var data = [
{ Animal: "Cats", Value: (Math.random() * 1000000) },
{ Animal: "Dogs", Value: (Math.random() * 1000000) },
{ Animal: "Mice", Value: (Math.random() * 1000000) }
];
var myChart = new dimple.chart(svg, data);
myChart.setBounds(60, 30, 510, 305)
var x = myChart.addCategoryAxis("x", "Animal");
x.addOrderRule(["Cats", "Dogs", "Mice"]);
myChart.addMeasureAxis("y", "Value");
myChart.addSeries("Animal", dimple.plot.bar);
var legend = myChart.addLegend(500,10,100, 100, "right");
myChart.draw();
d3.select("#btn").on("click", function() {
myChart.data = [
{ Animal: "Cats", Value: (Math.random() * 1000000) },
{ Animal: "Dogs", Value: (Math.random() * 1000000) },
{ Animal: "Mice", Value: (Math.random() * 1000000) }
];
myChart.draw(1000);
});
// filter
myChart.legends = [];
// Get a unique list of y values to use when filtering
var filterValues = dimple.getUniqueValues(data, "Animal");
// Get all the rectangles from our now orphaned legend
legend.shapes.selectAll('rect').on("click", function (e) {
// This indicates whether the item is already visible or not
var hide = false;
var newFilters = [];
//If the filters contain the clicked shape hide it
filterValues.forEach(function (f) {
if (f === e.aggField.slice(-1)[0]) {
hide = true;
} else {
newFilters.push(f);
}
});
if (hide) {
d3.select(this).style("opacity", 0.2);
} else {
newFilters.push(e.aggField.slice(-1)[0]);
d3.select(this).style("opacity", 0.8);
}
// // Update the filters
filterValues = newFilters;
//Filter the data
myChart.data = dimple.filterData(data, "Animal", filterValues);
myChart.draw(800);
});
Although the filtering happens as expected, it throws a d3 error on to the console:
Error: attribute x: Expected length, "NaN"
Any idea as to what may be causing this error?
Not sure what might be causing the error.
What my intuition tells me is that since the shapes from the previous set of data are being accessed when the chart is being redrawn with the new data since they're separate objects in memory. That is, the data generates the series which lives in the chart's svg object but is in no way tied to changes of the data until a chart is redrawn. If this is true, that could be why it's not finding values for the axes in order to draw the shapes that live there. (I wonder if there's a fail silently option in the future).
Anyway, if you are redrawing the chart, you could do this workaround:
if (oldChartData.length > newChartData.length) {
chart.svg.selectAll('*').remove();
createChart(newChartData);
}
It's dirty but it works.
Edit: Here's the related github issue.

Resources