I have a function I'm using to add new data points to my stock chart. I need to create a condition under which one of the data points comes with an icon. I see that stockEvents can do this but it's not showing in my chart:
function addDataPoint(ask) {
var dataProvider = chart.dataSets[0].dataProvider;
var newDate = new Date(dataProvider[dataProvider.length - 1].date.getTime());
newDate.setHours(newDate.getHours(), newDate.getMinutes() + 1, newDate.getSeconds());
var a = Math.round(Math.random() * (40 + 1000)) + 100 + 1000;
var b = Math.round(Math.random() * 100000000);
dataProvider.push({
date: newDate,
value: ask,
volume: ask
});
chart.dataSets[0].stockEvents = [{
date: newDate,
type: "flag",
text: "F",
description: "Some longer\ntext can also\n be added"
}];
dataProvider.shift();
}
You need to set the stock event's graph property in order for it to be visible. This can be a reference to the stock graph object, or the graph's id. You also need to call validateData in order to update the chart if you aren't already doing so outside of your addDataPoint function.
AmCharts.makeChart("chartdiv", {
// ...
"panels": [{
// ...
"stockGraphs": [{
// ...
"id": "g1", //added id
// ...
},
// ...
]
},
// ...
],
// ...
});
// ...
function addDataPoint(ask) {
// ...
chart.dataSets[0].stockEvents = [{
date: newDate,
type: "flag",
text: "F",
graph: "g1", //added
description: "Some longer\ntext can also\n be added"
}];
dataProvider.shift();
chart.validateData(); //added
}
Also note that you're overwriting the stockEvents array each time in your addDataPoint function. If you want to preserve your previous event, then you need to use push since it's an array.
Demo
Related
I am using Echarts, and I want to color my series (the trends, not the background) with different ranges. The following example is almost what I need:
But with two modifications:
I have a "time" axis instead of a "category" axis.
For the ranges of the series that are not specified in the visualMap, I want them to keep their original, random-generated color.
I just modified the code in the previous example to try to do it, with no luck:
var values = [];
for(var i = 0; i < 15; i++) {
var date = new Date();
date.setDate(date.getDate() + i);
values.push([date, i])
}
const firstDate = values[1][0];
const lastDate = values[5][0];
option = {
xAxis: {
type: 'time',
boundaryGap: false,
},
yAxis: {
type: 'value',
},
visualMap: {
pieces: [{
gt: firstDate,
lte: lastDate,
color: 'green'
}]
},
series: [
{
type: 'line',
data: values,
}
]
};
I just get errors like:
Uncaught DOMException: Failed to execute 'addColorStop' on 'CanvasGradient': The value provided ('undefined') could not be parsed as a color.
t.indexOf is not a function at py (echarts.min.js:formatted:15975)
I do not see any information related with time axes in the visualMap documentation.... any ideas?
I have a stacked bar chart made with C3.js which uses the following code to be generated:
stacked_bar_chart = c3.generate({
bindto: '#stacked_bar_chart_container',
data: {
columns: [
["Critical", 446, 863],
["High", 1160, 2301],
["Medium", 3106, 8258],
["Low", 277, 119],
["Informational", 7374, 23240]
],
type: 'bar',
groups: [
['Low', 'Medium', 'Informational', 'High', 'Critical', 'Unknown']
],
},
grid: {
y: {
lines: [{ value: 0 }]
}
},
axis: {
x: {
type: 'category',
categories: ["Remediated", "Unconfirmed"] // Notice the x-axis has categories
},
y: {
label: 'Number of Findings'
}
},
});
I am trying to make it so that at the click of a button, I am able to hide the bar called Remediated from the graph. I have tried to unload it by doing the following:
stacked_bar_chart.unload("Remediated");
but this has no effect, and I am pretty sure it is because I am using type: 'category' for the x-axis. I would prefer to not have to unload the data anyways so that later on I can re-display the bar as needed without retrieving the data again.
After some research in the C3.js reference page, I think that there is no easy API function for this to be accomplished, so I have come up with my own tested implementation of this feature that I am currently using.
Firstly, with the way that I do it I am keeping track of three separate global variables which will hold the data currently in the chart and also will hold the data we remove from it. This is the way I decided to choose because the data for my chart is coming from a web resource, so it would be inefficient to keep making AJAX calls and refreshing the data every time a category is added or removed.
// Our three new variables
var removed_from_stacked_bar = {};
var stacked_bar_categories = ["Remediated", "Unconfirmed"];
var stacked_bar_data = [
["Critical", 446, 863],
["High", 1160, 2301],
["Medium", 3106, 8258],
["Low", 277, 119],
["Informational", 7374, 23240]
];
function initialize_stacked_bar_chart(data, categories) {
stacked_bar_chart = c3.generate({
bindto: '#stacked_bar_chart_container',
data: {
columns: data, // Coming from the parameter
type: 'bar',
groups: [
['Low', 'Medium', 'Informational', 'High', 'Critical', 'Unknown']
],
},
grid: {
y: {
lines: [{ value: 0 }]
}
},
axis: {
x: {
type: 'category',
categories: categories // Coming from the parameter
},
y: {
label: 'Number of Findings'
}
},
});
}
initialize_stacked_bar_chart(stacked_bar_data, stacked_bar_categories);
Now I wrote a function called update_stacked_bar_chart() which has a category parameter in order to remove / add the category that is passed in from the chart whenever it is called.
function update_stacked_bar_chart(category) {
var categoryIndex = stacked_bar_categories.indexOf(category);
var removed_values = [];
if (categoryIndex != -1) { // Removing the item since it exists in the bar chart's categories
stacked_bar_categories.splice(categoryIndex, 1); // Removing the category name from the bar chart's category list
stacked_bar_data.forEach(function (item, index) {
var temp = item.splice(categoryIndex + 1, 1); // Removing the value this category held (in-place) in the sublist for each severity
removed_values.push(temp); // Pushing each removed value into the array of removed values (in order from Critical, High, Medium, Low, Informational).
});
removed_from_stacked_bar[category] = removed_values;
} else { // Re-adding the item if it was not found in the current chart's categories
stacked_bar_categories.push(category); // Adding the category name to the bar chart's category list
removed_from_stacked_bar[category].forEach(function (item, index) {
stacked_bar_data[index].push(item); // Adding the value for each severity into the respective severity list
});
delete removed_from_stacked_bar[category];
}
initialize_stacked_bar_chart(stacked_bar_data, stacked_bar_categories); // Remaking the bar chart with the new data and categories.
}
This function will allow you to toggle any category from your bar chart every time it is called. You can attach it to an event listener so that it is called as you need it.
Here is an example of how it can be used to toggle bars as it is called:
update_stacked_bar_chart("Remediated"); // Removes the "Remediated" bar
update_stacked_bar_chart("Remediated"); // Re-adds the "Remediated" bar
update_stacked_bar_chart("Remediated"); // Removes the "Remediated" bar
update_stacked_bar_chart("Unconfirmed"); // Removes the "Unconfirmed" bar
update_stacked_bar_chart("Remediated"); // Re-adds the "Remediated" bar
update_stacked_bar_chart("Unconfirmed"); // Re-adds the "Unconfirmed" bar
I start new project on AmCharts 4 maps, i want get data when i click on selected point. Now i have event on click but i don't know how get data from array, for example id and title.
// Create image series
var imageSeries = chart.series.push(new am4maps.MapImageSeries());
// Create a circle image in image series template so it gets replicated to all new images
var imageSeriesTemplate = imageSeries.mapImages.template;
var circle = imageSeriesTemplate.createChild(am4core.Circle);
//create circle/points
circle.radius = 5;
circle.fill = am4core.color("#000000");
circle.strokeWidth = 3;
circle.nonScaling = true;
circle.tooltipText = "{title}" + "{id}";
// Set prope fields
imageSeriesTemplate.propertyFields.latitude = "latitude";
imageSeriesTemplate.propertyFields.longitude = "longitude";
// example data cities
imageSeries.data = [{
"latitude": 48.856614,
"longitude": 2.352222,
"title": "Paris",
"id": 102
}, {
"latitude": 47.856614,
"longitude": 2.352222,
"title": "second Paris",
"id": 104
}];
//click on point event
circle.events.on("hit", function(ev) {
// HERE, WHAT CAN I DO??
console.log("clicked on ", ev.target);
}, this);
That would be:
imageSeriesTemplate.events.on("hit", (ev)=>{
console.log(ev.target.dataItem.dataContext.title)
})
#VermaAman, ran into the same problem, and found out the onHit should be added to the Circle, i'm using config, so it looks like this:
{
type: "MapImageSeries",
mapImages: {
children: [
{
type: "Circle",
radius: 4,
stroke: "#somecolor",
strokeWidth: 2,
nonScaling: true,
tooltipText: "{title}",
events: {
hit: function (ev: any) {
console.log(ev.target.dataItem.dataContext);
},
},
},
],
propertyFields: {
latitude: "latitude",
longitude: "longitude",
},
},
data: areas,
},
This image shows the result of my implementation.
The problem here is the feaure layer displayed in the map shows only one of the features passed in the code.
How have I done it?
Create a feature layer using new FeatureLayer(featureCollectionObject, options?).
Create a Query and QueryTask to request features from the arcgi server.
var selectQuery: Query = new Query();
selectQuery.returnGeometry = true;
selectQuery.where = "1=1";
selectQuery.outFields = ["NAME", "X", "Y"];
var queryTask_XZQH = new QueryTask(FL_XZQH_URL);
queryTask_XZQH.execute(selectQuery);
Define a event handler for "complete" of queryTask.
function onQueryTask_XZQHComplete(evt: object) {
console.log(evt.featureSet.geometryType);
//console.log(evt.featureSet);
FL_XZQH = new FeatureLayer({
featureSet: evt.featureSet,
layerDefinition: {
geometryType: "esriGeometryPolygon",
className: "xzqh",
objectIdField:"OBJECTID",
fields: [
{
name: "OBJECTID ",
type:"esriFieldTypeOID",
alias:"OBJECTID"
},
{
name: "ID ",
type:"esriFieldTypeInteger ",
alias:"Id"
},
{
name: "Name",
type: "esriFieldTypeString",
length: 50,
alias: "行政区划名称"
},
{
name: "X",
type: "esriFieldTypeDouble",
alias: "经度"
},
{
name: "Y",
type: "esriFieldTypeDouble",
alias: "纬度"
}
]
}
});
map.addLayer(FL_XZQH);
}
The result of QueryTask is fine, and the count of the features is 18.
However, when I use map.addLayer, the map just displays one feature.
The feature layer does not have a valid object ID. Make two changes to fix it:
Change this:
selectQuery.outFields = ["NAME", "X", "Y"];
To this (i.e. include the object ID in your query):
selectQuery.outFields = ["OBJECTID", "NAME", "X", "Y"];
Change this:
{
name: "OBJECTID ",
type:"esriFieldTypeOID",
alias:"OBJECTID"
},
To this (i.e. remove the space at the end of the field name):
{
name: "OBJECTID",
type:"esriFieldTypeOID",
alias:"OBJECTID"
},
Note: this will only work if the feature service actually has a field called OBJECTID.
is there any way to pass some additional data to the series object that will use to show in the chart 'tooltip'?
for example
tooltip: {
formatter: function() {
return '<b>'+ this.series.name +'</b><br/>'+
Highcharts.dateFormat('%b %e', this.x) +': '+ this.y;
}
here we can only use series.name , this.x & this.y to the series. lets say i need to pass another dynamic value alone with the data set and can access via series object. is this possible?
Thank you all in advance.
Yes, if you set up the series object like the following, where each data point is a hash, then you can pass extra values:
new Highcharts.Chart( {
...,
series: [ {
name: 'Foo',
data: [
{
y : 3,
myData : 'firstPoint'
},
{
y : 7,
myData : 'secondPoint'
},
{
y : 1,
myData : 'thirdPoint'
}
]
} ]
} );
In your tooltip you can access it via the "point" attribute of the object passed in:
tooltip: {
formatter: function() {
return 'Extra data: <b>' + this.point.myData + '</b>';
}
}
Full example here: https://jsfiddle.net/burwelldesigns/jeoL5y7s/
Additionally, with this solution, you can even put multiple data as much as you want :
tooltip: {
formatter: function () {
return 'Extra data: <b>' + this.point.myData + '</b><br> Another Data: <b>' + this.point.myOtherData + '</b>';
}
},
series: [{
name: 'Foo',
data: [{
y: 3,
myData: 'firstPoint',
myOtherData: 'Other first data'
}, {
y: 7,
myData: 'secondPoint',
myOtherData: 'Other second data'
}, {
y: 1,
myData: 'thirdPoint',
myOtherData: 'Other third data'
}]
}]
Thank you Nick.
For time series data, especially with enough data points to activate the turbo threshold, the proposed solutions above will not work. In the case of the turbo threshold, this is because Highcarts expects the data points to be an array like:
series: [{
name: 'Numbers over the course of time',
data: [
[1515059819853, 1],
[1515059838069, 2],
[1515059838080, 3],
// you get the idea
]
}]
In order not to lose the benefits of the turbo threshold (which is important when dealing with lots of data points), I store the data outside of the chart and look up the data point in the tooltip formatter function. Here's an example:
const chartData = [
{ timestamp: 1515059819853, value: 1, somethingElse: 'foo'},
{ timestamp: 1515059838069, value: 2, somethingElse: 'bar'},
{ timestamp: 1515059838080, value: 3, somethingElse: 'baz'},
// you get the idea
]
const Chart = Highcharts.stockChart(myChart, {
// ...options
tooltip: {
formatter () {
// this.point.x is the timestamp in my original chartData array
const pointData = chartData.find(row => row.timestamp === this.point.x)
console.log(pointData.somethingElse)
}
},
series: [{
name: 'Numbers over the course of time',
// restructure the data as an array as Highcharts expects it
// array index 0 is the x value, index 1 is the y value in the chart
data: chartData.map(row => [row.timestamp, row.value])
}]
})
This approach will work for all chart types.
I am using AJAX to get my data from SQL Server, then I prepare a js array that is used as the data in my chart.
JavaScript code once the AJAX is successfull:
...,
success: function (data) {
var fseries = [];
var series = [];
for (var arr in data) {
for (var i in data[arr]['data'] ){
var d = data[arr]['data'][i];
//if (i < 5) alert("d.method = " + d.method);
var serie = {x:Date.parse(d.Value), y:d.Item, method:d.method };
series.push(serie);
}
fseries.push({name: data[arr]['name'], data: series, location: data[arr]['location']});
series = [];
};
DrawChart(fseries);
},
Now to show extra meta-data in the tooltip:
...
tooltip: {
xDateFormat: '%m/%d/%y',
headerFormat: '<b>{series.name}</b><br>',
pointFormat: 'Method: {point.method}<br>Date: {point.x:%m/%d/%y } <br>Reading: {point.y:,.2f}',
shared: false,
},
I use a DataRow to iterate through my result set, then I use a class to assign the values prior to passing back in Json format. Here is the C# code in the controller action called by Ajax.
public JsonResult ChartData(string dataSource, string locationType, string[] locations, string[] methods, string fromDate, string toDate, string[] lstParams)
{
List<Dictionary<string, object>> dataResult = new List<Dictionary<string, object>>();
Dictionary<string, object> aSeries = new Dictionary<string, object>();
string currParam = string.Empty;
lstParams = (lstParams == null) ? new string[1] : lstParams;
foreach (DataRow dr in GetChartData(dataSource, locationType, locations, methods, fromDate, toDate, lstParams).Rows)
{
if (currParam != dr[1].ToString())
{
if (!String.IsNullOrEmpty(currParam)) //A new Standard Parameter is read and add to dataResult. Skips first record.
{
Dictionary<string, object> bSeries = new Dictionary<string, object>(aSeries); //Required else when clearing out aSeries, dataResult values are also cleared
dataResult.Add(bSeries);
aSeries.Clear();
}
currParam = dr[1].ToString();
aSeries["name"] = cParam;
aSeries["data"] = new List<ChartDataModel>();
aSeries["location"] = dr[0].ToString();
}
ChartDataModel lst = new ChartDataModel();
lst.Value = Convert.ToDateTime(dr[3]).ToShortDateString();
lst.Item = Convert.ToDouble(dr[2]);
lst.method = dr[4].ToString();
((List<ChartDataModel>)aSeries["data"]).Add(lst);
}
dataResult.Add(aSeries);
var result = Json(dataResult.ToList(), JsonRequestBehavior.AllowGet); //used to debug final dataResult before returning to AJAX call.
return result;
}
I realize there is a more efficient and acceptable way to code in C# but I inherited the project.
Just to add some kind of dynamism :
Did this for generating data for a stacked column chart with 10 categories.
I wanted to have for each category 4 data series and wanted to display additional information (image, question, distractor and expected answer) for each of the data series :
<?php
while($n<=10)
{
$data1[]=array(
"y"=>$nber1,
"img"=>$image1,
"ques"=>$ques,
"distractor"=>$distractor1,
"answer"=>$ans
);
$data2[]=array(
"y"=>$nber2,
"img"=>$image2,
"ques"=>$ques,
"distractor"=>$distractor2,
"answer"=>$ans
);
$data3[]=array(
"y"=>$nber3,
"img"=>$image3,
"ques"=>$ques,
"distractor"=>$distractor3,
"answer"=>$ans
);
$data4[]=array(
"y"=>$nber4,
"img"=>$image4,
"ques"=>$ques,
"distractor"=>$distractor4,
"answer"=>$ans
);
}
// Then convert the data into data series:
$mydata[]=array(
"name"=>"Distractor #1",
"data"=>$data1,
"stack"=>"Distractor #1"
);
$mydata[]=array(
"name"=>"Distractor #2",
"data"=>$data2,
"stack"=>"Distractor #2"
);
$mydata[]=array(
"name"=>"Distractor #3",
"data"=>$data3,
"stack"=>"Distractor #3"
);
$mydata[]=array(
"name"=>"Distractor #4",
"data"=>$data4,
"stack"=>"Distractor #4"
);
?>
In the highcharts section:
var mydata=<? echo json_encode($mydata)?>;
// Tooltip section
tooltip: {
useHTML: true,
formatter: function() {
return 'Question ID: <b>'+ this.x +'</b><br/>'+
'Question: <b>'+ this.point.ques +'</b><br/>'+
this.series.name+'<br> Total attempts: '+ this.y +'<br/>'+
"<img src=\"images/"+ this.point.img +"\" width=\"100px\" height=\"50px\"/><br>"+
'Distractor: <b>'+ this.point.distractor +'</b><br/>'+
'Expected answer: <b>'+ this.point.answer +'</b><br/>';
}
},
// Series section of the highcharts
series: mydata
// For the category section, just prepare an array of elements and assign to the category variable as the way I did it on series.
Hope it helps someone.