amCharts 4: External Data Source - amcharts

I'm struggling to generate a simple XY horizontal bar chart in amCharts 4 using an external data source.
Everything works fine with static data (where I adapted one of the chart demos from the amCharts documentation).
But when I replace the static data with the external dataSource URL, the chart won't generate.
I suspect that the problem might be the Airtable JSON output is a different structure to the static data.
However, I could be completely wrong on that suspicion, so I'd really appreciate any help to a solution please.
(I've obviously XXX'd out the Airtable information in the code below.)
Static Data Source
// Create chart instance
var chart = am4core.create("CHARTDIV", am4charts.XYChart);
// Add data
chart.data = [{
"Name": "Brand Guidelines",
"Aggregate Responses": 7
}, {
"Name": "SAP",
"Aggregate Responses": 3
}, {
"Name": "Email",
"Aggregate Responses": 5
}, {
"Name": "Social Media",
"Aggregate Responses": 3
}, {
"Name": "Google Drive",
"Aggregate Responses": 3
}, {
"Name": "OneDrive",
"Aggregate Responses": 4
}, {
"Name": "SharePoint",
"Aggregate Responses": 1
}, {
"Name": "Slack",
"Aggregate Responses": 3
}, {
"Name": "Drupal",
"Aggregate Responses": 2
}, {
"Name": "Telephone",
"Aggregate Responses": 3
}];
// Create axes
var categoryAxis = chart.yAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "Name";
categoryAxis.renderer.grid.template.location = 0;
categoryAxis.renderer.minGridDistance = 30;
var valueAxis = chart.xAxes.push(new am4charts.ValueAxis());
// Create series
var series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.valueX = "Aggregate Responses";
series.dataFields.categoryY = "Name";
series.name = "Aggregate Responses";
series.columns.template.tooltipText = "{categoryY}: [bold]{valueX}[/]";
series.columns.template.fillOpacity = .8;
var columnTemplate = series.columns.template;
columnTemplate.strokeWidth = 2;
columnTemplate.strokeOpacity = 1;
External Data Source
// Create chart instance
var chart = am4core.create("CHARTDIV", am4charts.XYChart);
// External data source
chart.dataSource.url = "https://api.airtable.com/v0/appXXXXXXX/airtable-table-name?api_key=keyXXXXXXX";
// Create axes
var categoryAxis = chart.yAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "Name";
categoryAxis.renderer.grid.template.location = 0;
categoryAxis.renderer.minGridDistance = 30;
var valueAxis = chart.xAxes.push(new am4charts.ValueAxis());
// Create series
var series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.valueX = "Aggregate Responses";
series.dataFields.categoryY = "Name";
series.name = "Aggregate Responses";
series.columns.template.tooltipText = "{categoryY}: [bold]{valueX}[/]";
series.columns.template.fillOpacity = .8;
var columnTemplate = series.columns.template;
columnTemplate.strokeWidth = 2;
columnTemplate.strokeOpacity = 1;
Airtable JSON Output
{
"records": [
{
"id": "recXXXXXX",
"fields": {
"Name": "EpiServer",
"Typeform Responses": [
"recXXXXXX",
"recXXXXXX",
"recXXXXXX"
],
"Aggregate Responses": 3
},
"createdTime": "2020-05-22T16:11:01.000Z"
},
{
"id": "recXXXXXX",
"fields": {
"Name": "OneDrive",
"Typeform Responses": [
"recXXXXXX",
"recXXXXXX",
"recXXXXXX",
"recXXXXXX"
],
"Aggregate Responses": 4
},
"createdTime": "2020-05-22T16:50:41.000Z"
},
]
}

Your suspicion about the format being different is the right one. AmCharts needs a simple, flattened array of objects where it can find the fields you've specified, so you'll need to convert your external data into a format that AmCharts can accept. You can use the parseended event to re-map your data like so:
chart.dataSource.events.on("parseended", function(ev) {
// parsed data is assigned to data source's `data` property
// pluck data from the records array in your raw data
ev.target.data = ev.target.data.records.map(function(dataItem) {
return {
"Aggregate Responses": dataItem.fields["Aggregate Responses"],
"Name": dataItem.fields.Name
}
});
});
Example below using your code and a base64 encoded data URL of your sample data as demonstration:
// Create chart instance
var chart = am4core.create("CHARTDIV", am4charts.XYChart);
// External data source
//chart.dataSource.url = "https://api.airtable.com/v0/appXXXXXXX/airtable-table-name?api_key=keyXXXXXXX";
chart.dataSource.url = dataURI(); //fake URL for demonstration purposes
chart.dataSource.events.on("parseended", function(ev) {
// parsed data is assigned to data source's `data` property
ev.target.data = ev.target.data.records.map(function(dataItem) {
return {
"Aggregate Responses": dataItem.fields["Aggregate Responses"],
"Name": dataItem.fields.Name
}
});
});
// Create axes
var categoryAxis = chart.yAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "Name";
categoryAxis.renderer.grid.template.location = 0;
categoryAxis.renderer.minGridDistance = 30;
var valueAxis = chart.xAxes.push(new am4charts.ValueAxis());
// Create series
var series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.valueX = "Aggregate Responses";
series.dataFields.categoryY = "Name";
series.name = "Aggregate Responses";
series.columns.template.tooltipText = "{categoryY}: [bold]{valueX}[/]";
series.columns.template.fillOpacity = .8;
var columnTemplate = series.columns.template;
columnTemplate.strokeWidth = 2;
columnTemplate.strokeOpacity = 1;
// base64 encoded version of sample data as a data URI
function dataURI() {
return "data:application/json;base64,eyAicmVjb3JkcyI6IFsgeyAiaWQiOiAicmVjWFhYWFhYIiwgImZpZWxkcyI6IHsgIk5hbWUiOiAiRXBpU2VydmVyIiwgIlR5cGVmb3JtIFJlc3BvbnNlcyI6IFsgInJlY1hYWFhYWCIsICJyZWNYWFhYWFgiLCAicmVjWFhYWFhYIiBdLCAiQWdncmVnYXRlIFJlc3BvbnNlcyI6IDMgfSwgImNyZWF0ZWRUaW1lIjogIjIwMjAtMDUtMjJUMTY6MTE6MDEuMDAwWiIgfSwgeyAiaWQiOiAicmVjWFhYWFhYIiwgImZpZWxkcyI6IHsgIk5hbWUiOiAiT25lRHJpdmUiLCAiVHlwZWZvcm0gUmVzcG9uc2VzIjogWyAicmVjWFhYWFhYIiwgInJlY1hYWFhYWCIsICJyZWNYWFhYWFgiLCAicmVjWFhYWFhYIiBdLCAiQWdncmVnYXRlIFJlc3BvbnNlcyI6IDQgfSwgImNyZWF0ZWRUaW1lIjogIjIwMjAtMDUtMjJUMTY6NTA6NDEuMDAwWiIgfSBdIH0=";
}
<script src="//www.amcharts.com/lib/4/core.js"></script>
<script src="//www.amcharts.com/lib/4/charts.js"></script>
<div id="CHARTDIV" style="width: 100%; height: 98vh"></div>
Make sure your data is valid JSON as your sample has an incorrect trailing comma.

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]);
});

Adaptive card column style is working in Adaptive Designer but not in WebChat

I'm using Adaptive Card version 1.2 and I'm trying to add style for Adaptive Column. The style is getting reflected in Adaptive Designer(Web Chat). But the same if I try to check in hosted Web Chat styles are not getting reflected. Also I would like to mention that I'm creating Adaptive card using C#.
But it is looking like this
The i'm using this for webchat (https://cdn.botframework.com/botframework-webchat/latest/webchat.js)
Here is my C# code
AdaptiveCard card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0));
card.Body.Add(new AdaptiveTextBlock()
{
Text = $"Hello User",
HorizontalAlignment = AdaptiveHorizontalAlignment.Center
});
card.Body.Add(new AdaptiveTextBlock()
{
FontType = AdaptiveFontType.Monospace,
Text = "Would you like to rate us",
Color = AdaptiveTextColor.Good,
Weight = AdaptiveTextWeight.Bolder,
HorizontalAlignment = AdaptiveHorizontalAlignment.Center,
Separator = true
});
card.Body.Add(new AdaptiveColumnSet()
{
Separator = true,
Columns = new List<AdaptiveColumn>()
{
new AdaptiveColumn()
{
Bleed = true,
Width = AdaptiveColumnWidth.Stretch,
Style = AdaptiveContainerStyle.Emphasis,
SelectAction = new AdaptiveSubmitAction()
{
Title = "1",
DataJson = "{ \"Type\": \"1\" }"
},
Items = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = "1",
}
}
},
new AdaptiveColumn()
{
Bleed = true,
Width = AdaptiveColumnWidth.Stretch,
Style = AdaptiveContainerStyle.Accent,
SelectAction = new AdaptiveSubmitAction()
{
Title = "2",
DataJson = "{ \"Type\": \"2\" }"
},
Items = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = $"2"
}
}
},
new AdaptiveColumn()
{
Bleed = true,
Width = AdaptiveColumnWidth.Stretch,
Style = AdaptiveContainerStyle.Good,
SelectAction = new AdaptiveSubmitAction()
{
Title = "3",
DataJson = "{ \"Type\": \"3\" }"
},
Items = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = $"3"
}
}
},
new AdaptiveColumn()
{
Bleed = true,
Width = AdaptiveColumnWidth.Stretch,
Style = AdaptiveContainerStyle.Attention,
SelectAction = new AdaptiveSubmitAction()
{
Title = "4",
DataJson = "{ \"Type\": \"4\" }"
},
Items = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = $"4"
}
}
}
}
});
Please help me on this

amcharts angular zoomlevelchanged event

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();
}
});
}
}

Flat the nested data into the top level object Amcharts

Here is the structure of data1
var data1 = [{
"Date": "2016-07-09",
"StockList": [{"Name": "H1", "PNL2": 20, "NAV2" : 20}, {"Name": "H2", "PNL2": 20, "NAV2" : 20}]
"NAV": 26.28,
"PNL": 7.61
}, {
"Date": "2016-07-10",
"StockList": [{"Name": "H1", "PNL2": 20, "NAV2" : 20}, {"Name": "H2", "PNL2": 20, "NAV2" : 20}]
"NAV": 27.55,
"PNL": 12.89
}];
since nested data is not allowed in the Amcharts(if there is a way to deal with the nested data, that is better), is it possible flat the into the top level object(I can not change the original inputed data). For example:
var data1 = [{
"Date": "2016-07-09",
"H1_PNL2": 20,
"H1_NAV2" : 20
"H2_PNL2": 20,
"H2_NAV2" : 20,
"NAV": 26.28,
"PNL": 7.61
}, {
"Date": "2016-07-10",
"H1_PNL2": 20,
"H1_NAV2" : 20
"H2_PNL2": 20,
"H2_NAV2" : 20,
"NAV": 27.55,
"PNL": 12.89
}];
In such Amcharts code:
var dataSet1 = new AmCharts.DataSet();
dataSet1.color = "#b0de09";
//create your field mappings for each valueField
dataSet1.fieldMappings = valueFields.map(function(valueField) {
return {
fromField: valueField,
toField: valueField
};
});
dataSet1.dataProvider = data;
dataSet1.categoryField = "Date";
chart.dataSets = [dataSet1];
// PANELS
var stockPanel = new AmCharts.StockPanel();
stockPanel.showCategoryAxis = true;
stockPanel.title = "PNL2";
stockPanel.eraseAll = false;
//stockPanel.addLabel(0, 100, "Click on the pencil icon on top-right to start drawing", "center", 16);
//create a graph for each valueField
valueFields.forEach(function(valueField) {
var graph = new AmCharts.StockGraph();
graph.title = valueField;
graph.valueField = valueField;
graph.bullet = "round";
graph.bulletColor = "#FFFFFF";
graph.bulletBorderColor = "#00BBCC";
graph.bulletBorderAlpha = 1;
graph.bulletBorderThickness = 2;
graph.bulletSize = 7;
graph.lineThickness = 2;
graph.lineColor = "#00BBCC";
graph.useDataSetColors = false;
stockPanel.addStockGraph(graph);
});
chart.addPanel(stockPanel);
chart.write("chartdiv");
}
createStockChart(data1);
Here is my data structure
[{,…}, {,…}, {,…}, {,…}, {,…}, {,…}, {,…}, {,…}, {,…}, {,…}, {,…}, {,…}, {,…}, {,…}, {,…}, {,…}, {,…},…]
[0 … 99]
0:{,…}
CashLeft:0
Commission:0
Date:"/Date(1454428800000)/"
NAV:0
PNL:0
Performance:[{
0:{Stock: {Symbol: "1044 HK", Close: 66.714, Date: "/Date(1454428800000)/"}, Position: 0,…}
1:{Stock: {Symbol: "1088 HK", Close: 10.9, Date: "/Date(1454428800000)/"}, Position: 0,…}
2:{Stock: {Symbol: "12 HK", Close: 35.955, Date: "/Date(1454428800000)/"}, Position: 0,…}
And here is the code I write down
function createStockChart(data) {
var chart = new AmCharts.AmStockChart();
data.forEach(function (data1) {
data1.Performance.forEach(function (stockItem) {
data1[stockItem.Symbol + "_PNL2"] = stockItem.PNL2;
});
//the following are completely optional - AmCharts won't look at StockList, but if you
//want to conserve memory and don't need the StockList for anything else, you can remove
//it
data1.StockList.length = 0; //delete the StockList Array. Not necessary if you need it for something else
delete data1.StockList; //remove the property from the object if you want. Also not necessary.
});
But when I see the data structure it does not change, Maybe here is the problem that the List of stock(I mean the name(Symbol)) will be changed some days later, that means the properties of data will changed periodically, I don't know whether it is the point?
Assuming your StockList will always have a Name, PNL2 and NAV2, you can just loop through your data and assign the properties and values to the object prior to the rest of your code.
data1.forEach(function(arrayElement) {
arrayElement.StockList.forEach(function(stockItem) {
arrayElement[stockItem.Name + "_PNL2"] = stockItem.PNL2;
arrayElement[stockItem.Name + "_NAV2"] = stockItem.NAV2;
});
//the following are completely optional - AmCharts won't look at StockList, but if you
//want to conserve memory and don't need the StockList for anything else, you can remove
//it
arrayElement.StockList.length = 0; //delete the StockList Array. Not necessary if you need it for something else
delete arrayElement.StockList; //remove the property from the object if you want. Also not necessary.
});
From there you can just run the rest of your code from my previous answer.
Demo:
var data1 = [{
"Date": "2016-07-09",
"StockList": [{
"Name": "H1",
"PNL2": 20,
"NAV2": 20
}, {
"Name": "H2",
"PNL2": 20,
"NAV2": 20
}],
"NAV": 26.28,
"PNL": 7.61
}, {
"Date": "2016-07-10",
"StockList": [{
"Name": "H1",
"PNL2": 20,
"NAV2": 20
}, {
"Name": "H2",
"PNL2": 20,
"NAV2": 20
}],
"NAV": 27.55,
"PNL": 12.89
}];
data1.forEach(function(arrayElement) {
arrayElement.StockList.forEach(function(stockItem) {
arrayElement[stockItem.Name + "_PNL2"] = stockItem.PNL2;
arrayElement[stockItem.Name + "_NAV2"] = stockItem.NAV2;
});
//the following are completely optional - AmCharts won't look at StockList, but if you
//want to conserve memory and don't need the StockList for anything else, you can remove
//it
arrayElement.StockList.length = 0; //delete the StockList Array. Not necessary if you need it for something else
delete arrayElement.StockList; //remove the property from the object if you want. Also not necessary.
});
console.log(data1);

Center Kendo Map between markers

Is there any way to center map at the center between markers? Making its center between markers, talking into account that markers is loaded from remote dataSource?
This can be done based on the example here found here: http://www.kendoui.io/kendo-ui/dataviz/map/how-to/zoom-on-area.html
The code is as follows
<button id="center">Center on markers</button>
<div id="map"></div>
<script>
var markers = [
{"latlng":[30.2675,-97.7409], "name": "Zevo Toys"},
{"latlng": [30.2707,-97.7490],"name": "Foo Bars"},
{"latlng": [30.2705,-97.7409],"name": "Mainway Toys"},
{"latlng": [30.2686,-97.7494], "name": "Acme Toys"}];
$("#map").kendoMap({
layers: [{
type: "tile",
urlTemplate: "http://#= subdomain #.tile.openstreetmap.org/#= zoom #/#= x #/#= y #.png",
subdomains: ["a", "b", "c"],
attribution: "© <a href='http://osm.org/copyright'>OpenStreetMap contributors</a>."
}, {
type: "marker",
dataSource: {
data: markers
},
locationField: "latlng",
titleField: "name"
}]
});
function centerMap() {
var map = $("#map").getKendoMap();
var layer = map.layers[1];
var markers = layer.items;
var extent;
for (var i = 0; i < markers.length; i++) {
var loc = markers[i].location();
if (!extent) {
extent = new kendo.dataviz.map.Extent(loc, loc);
} else {
extent.include(loc);
}
}
map.extent(extent);
}
$("#center").click(centerMap);
</script>

Resources