How to generate snapshot of amCharts without visiting browser - javascript

I am using amCharts library on my website. On the side block I am displaying chart snapshots (thumbnails) and if clicked it goes to the chart itself. To display these snapshots they should be pre-generated but only way I can do it when I am on a chart page. Also I need to update these snapshots by schedule every day. I need a way (if possible) to generate these snapshots without visiting a webpage, ideally through command line.
At the moment I am saving charts as images on the server-side:
<script src="/static/amcharts/amcharts.js" type="text/javascript"></script>
<script src="/static/amcharts/serial.js" type="text/javascript"></script>
<script src="/static/amcharts/amstock.js" type="text/javascript"></script>
<script src="/static/amcharts/exporting/amexport.js" type="text/javascript"></script>
<script src="/static/amcharts/exporting/rgbcolor.js" type="text/javascript"></script>
<script src="/static/amcharts/exporting/canvg.js" type="text/javascript"></script>
<script src="/static/amcharts/exporting/filesaver.js" type="text/javascript"></script>
<script>
var cdata = {{ chartData|safe }};
AmCharts.ready(function () {
generateChartData();
createStockChart();
});
var chartData = [];
function generateChartData() {
for (var i = 0; i < cdata.length; i++)
{
chartData.push({
date: new Date(cdata[i]["date"]),
value: cdata[i]["value"],
volume: cdata[i]["volume"]
});
}
}
var chart;
function createStockChart() {
chart = new AmCharts.AmStockChart();
chart.exportConfig = {
menuItems: []
};
chart.addListener('rendered', function (event) {
chart.AmExport.output({format:"png", output: 'datastring'},
function(data) {
$.post("/charts/save_chart_snapshot/", {
imageData: encodeURIComponent(data)
});
});
});
chart.pathToImages = "/static/amcharts/images/";
var categoryAxesSettings = new AmCharts.CategoryAxesSettings();
categoryAxesSettings.minPeriod = "DD";
chart.categoryAxesSettings = categoryAxesSettings;
// DATASETS //////////////////////////////////////////
var dataSet = new AmCharts.DataSet();
dataSet.color = "#9cc11a";
dataSet.fieldMappings = [{
fromField: "value",
toField: "value"
}, {
fromField: "volume",
toField: "volume"
}];
dataSet.dataProvider = chartData;
dataSet.categoryField = "date";
// set data sets to the chart
chart.dataSets = [dataSet];
// PANELS ///////////////////////////////////////////
// first stock panel
var stockPanel1 = new AmCharts.StockPanel();
stockPanel1.showCategoryAxis = false;
stockPanel1.title = "Value";
stockPanel1.percentHeight = 70;
// graph of first stock panel
var graph1 = new AmCharts.StockGraph();
graph1.valueField = "value";
graph1.type = "smoothedLine";
graph1.lineThickness = 2;
graph1.bullet = "round";
graph1.bulletBorderColor = "#FFFFFF";
graph1.bulletBorderAlpha = 1;
graph1.bulletBorderThickness = 3;
stockPanel1.addStockGraph(graph1);
// create stock legend
var stockLegend1 = new AmCharts.StockLegend();
stockLegend1.valueTextRegular = " ";
stockLegend1.markerType = "none";
stockPanel1.stockLegend = stockLegend1;
// second stock panel
var stockPanel2 = new AmCharts.StockPanel();
stockPanel2.title = "Volume";
stockPanel2.percentHeight = 30;
var graph2 = new AmCharts.StockGraph();
graph2.valueField = "volume";
graph2.type = "column";
graph2.cornerRadiusTop = 2;
graph2.fillAlphas = 1;
stockPanel2.addStockGraph(graph2);
// create stock legend
var stockLegend2 = new AmCharts.StockLegend();
stockLegend2.valueTextRegular = " ";
stockLegend2.markerType = "none";
stockPanel2.stockLegend = stockLegend2;
// set panels to the chart
chart.panels = [stockPanel1, stockPanel2];
// OTHER SETTINGS ////////////////////////////////////
var scrollbarSettings = new AmCharts.ChartScrollbarSettings();
scrollbarSettings.graph = graph1;
scrollbarSettings.updateOnReleaseOnly = true;
scrollbarSettings.usePeriod = "WW"; // this will improve performance
scrollbarSettings.position = "top";
chart.chartScrollbarSettings = scrollbarSettings;
var cursorSettings = new AmCharts.ChartCursorSettings();
cursorSettings.valueBalloonsEnabled = true;
chart.chartCursorSettings = cursorSettings;
// PERIOD SELECTOR ///////////////////////////////////
var periodSelector = new AmCharts.PeriodSelector();
periodSelector.position = "top";
periodSelector.dateFormat = "YYYY-MM-DD";
periodSelector.inputFieldWidth = 150;
periodSelector.periods = [{
period: "DD",
count: 1,
label: "1 day"
}, {
period: "DD",
count: 2,
label: "2 days"
}, {
period: "DD",
count: 5,
label: "5 days"
}, {
period: "DD",
count: 12,
label: "12 days"
}, {
period: "MAX",
label: "MAX"
}];
chart.periodSelector = periodSelector;
var panelsSettings = new AmCharts.PanelsSettings();
panelsSettings.usePrefixes = true;
chart.panelsSettings = panelsSettings;
chart.write('chartdiv');
}
</script>
And this is how I save it on server-side:
def save_chart_snapshot(request):
if request.POST:
data = urllib.unquote(request.POST['imageData'])
imageData = data.split(';')[1].split(',')[1]
filename = "static/temp/charts/snapshots/" + request.POST['isin'] + ".png"
if not os.path.exists(filename):
try:
fh = open(filename, "wb")
fh.write(imageData.decode('base64'))
except IOError:
logger.error("Cannot find file or read data: " + filename)
else:
fh.close()
logger.debug("Chart snapshot is generated: " + filename)
return HttpResponse("done")

This is a bit late but I ran into a similar situation recently where I needed export report capabilities at the server-side and wanted to use amcharts.js. As I was exploring, I found that it is possible to run amcharts using puppeteer that provides a headless browser.
Below article explains it in detail:
https://www.amcharts.com/docs/v4/tutorials/automating-report-generation-using-puppeteer/
There's also a downloadable example here.
Hope this helps someone looking for similar functionality.

You can probably automate this process by visiting your charts page using a WebKit "browser" in a virtual framebuffer using a tool like uzbl-core or netserf or wkhtmltopdf or similar (all of those support JS).
Then just add the above task to a script in a cron job that's kicked-off every day.
Technically you're still visiting the page in a browser with this solution, but at least it can be reasonably automated from a headless server in a datacenter.

Related

amCharts 4: sum of values does not work when loading external data

I am loading an external data and values are working on series and tooltips pretty fine but the main value that is the sum is returning "undefined" as your see:
Here is the working code chart when I use static data. I put // in the dataSource that I am using to load the external data.
am4core.useTheme(am4themes_animated); //Theme
var chart = am4core.create("chartdiv", am4charts.PieChart);
chart.hiddenState.properties.opacity = 0; // Cria o fade-in inicial
chart.data = [
{
tipo: "Call",
value: 347548256.09
},
{
tipo: "Put",
value: 424644842.25
}
]
//chart.dataSource.url = "http://www.stock1.com.br/public/js/datasource/volume_opcoes.json";
chart.language.locale = am4lang_pt_BR;
/*
chart.numberFormatter.numberFormat = "#a";
chart.numberFormatter.bigNumberPrefixes = [
{ "number": 1e+3, "suffix": "K" },
{ "number": 1e+6, "suffix": "M" },
{ "number": 1e+9, "suffix": "B" }
];
*/
chart.radius = am4core.percent(70);
chart.innerRadius = am4core.percent(40);
chart.startAngle = 180;
chart.endAngle = 360;
var series = chart.series.push(new am4charts.PieSeries());
series.dataFields.value = "value";
series.dataFields.category = "tipo";
series.slices.template.cornerRadius = 10;
series.slices.template.innerCornerRadius = 7;
series.slices.template.inert = true;
series.slices.template.stroke = new am4core.InterfaceColorSet().getFor("background");
series.slices.template.strokeWidth = 1;
series.slices.template.strokeOpacity = 1;
series.slices.template.tooltipText = "R$ {value.value}";
series.slices.template.tooltipText.fill = am4core.color("#ffffff");
series.alignLabels = false;
series.labels.template.bent = true;
series.labels.template.radius = 8;
series.labels.template.padding(0,0,0,0);
series.labels.template.fill = am4core.color("#475F7B");
series.hiddenState.properties.startAngle = 90;
series.hiddenState.properties.endAngle = 90;
var label = chart.seriesContainer.createChild(am4core.Label);
label.fill = am4core.color("#475F7B");
label.textAlign = "middle";
label.horizontalCenter = "middle";
label.verticalCenter = "middle";
label.adapter.add("text", function(text, target) {
return "[bold font-size:18px]" + "R$" + series.dataItem.values.value.sum + "\n" + "[font-size:14px]Volume Total[/]";
});
<script src="https://cdn.amcharts.com/lib/4/core.js"></script>
<script src="https://cdn.amcharts.com/lib/4/charts.js"></script>
<script src="https://cdn.amcharts.com/lib/4/themes/animated.js"></script>
<script src="//www.amcharts.com/lib/4/lang/pt_BR.js"></script>
<div id="chartdiv"></div>
Here you see the sum working because I am loading the data right here. This snippet doesn't let me use my json. By the way, the one I am trying to load is: json.
A second problem I am facing is the reduction of large numbers that is not working as it should. I let commented on the snippet above.
Does anyone know where I'm going wrong?
After a long searching and reading amCharts 4 documentation, here is the answer:
The "undefined" was solved by changing the var label and removing the label.adapter.add to:
var label = series.createChild(am4core.Label);
label.text = "[bold font-size:16px]R${values.value.sum}\n[normal]Volume Total";
label.fill = am4core.color("#475F7B");
label.horizontalCenter = "middle";
label.verticalCenter = "bottom";
The second problem, about the Big Numbers, was solved changing the chart.numberFormatter.numberFormat = "#a"; to chart.numberFormatter.numberFormat = "#.0a";.

Acrobat dynamic stamp popup window to reflect stamp comment

I am creating an application with an acrobat dynamic stamp pop up window and I would like it to reflect the stamp comment. My dynamic stamp has some JavaScript that will generate a pop-up window. The information on the pop-up window text field will become part of the stamp. I'm trying to add the contents of the pop-up window in two areas.
On the Dynamic stamp (done)
On the Stamp Comments (need help)
Below I added the JavaScript I currently have. If anyone here can help me find a solution, I'd really appreciate it.
var builder =
{
// These map to Text Fields in the Stamp
textBoxes :
[
{ field:"IsoNum", description:"Isometric Number:", default:function() { return Collab.user; } }
]
}
/*********** belongs to: AcroForm:Calculation:Calculate ***********/
// SEE GLOBAL JAVASCRIPT SECTION FOR CUSTOMIZATION
if (event.source.forReal)
{
var stampDialog = CreateDialog(builder);
app.execDialog(stampDialog);
for (var i = 0; i < builder.textBoxes.length; ++i)
{
var t = builder.textBoxes[i];
this.getField(t.field).value = stampDialog.textBoxResults[i];
}
}
function CreateDialog(dialogBuilder)
{
var sd = new Object();
sd.builder = dialogBuilder;
sd.textBoxResults = new Array();
var optionsElements = new Array();
for (var i = 0; i < dialogBuilder.textBoxes.length; ++i)
{
var view = new Object();
view.type = "view";
view.align_children = "align_row";
view.elements = new Array();
var t = dialogBuilder.textBoxes[i];
var s = new Object();
s.type = "static_text";
s.item_id = "sta" + i;
s.name = t.description;
s.width = 110;
var e = new Object();
e.type = "edit_text";
e.item_id = "edt" + i;
e.width = 150;
view.elements[0] = s;
view.elements[1] = e;
optionsElements[i] = view;
}
var optionsCluster =
{
type: "cluster",
name: "Options",
elements: optionsElements
};
sd.initialize = function(dialog)
{
var init = new Object();
for (var i = 0; i < this.builder.textBoxes.length; ++i)
{
var t = this.builder.textBoxes[i];
var id = "edt" + i;
init[id] = t.default();
}
dialog.load(init);
};
sd.commit = function(dialog)
{
var res = dialog.store();
for (var i = 0; i < this.builder.textBoxes.length; ++i)
{
var t = this.builder.textBoxes[i];
var id = "edt" + i;
this.textBoxResults[i] = res[id];
}
};
sd.description =
{
name: "Stamp Dialog",
elements:
[
{
type: "view",
align_children: "align_fill",
elements:
[
optionsCluster
]
},
{
type: "ok"
}
]
};
return sd;
}
I don't have any specific code but it seems you understand enough Acrobat JavaScript to understand my instructions.
The dialog code runs between the time you select and position the stamp and when the stamp actually gets created. For that reason, you can't set the contents of the note directly in your code because the stamp annotation doesn't actually exist until your commit function finishes. What you have to do instead is create a function that sets the contents of the note inside a timeOut and use a delay of about a half second.

Get active_id in javascript Odoo 10

Hello I'm trying to pass my active_id from my res.partner to the JS.
But no luck.
I tried with dataset active_id but it's always undefined.
and i can't find any solution in the docs.
Here's my JS File:
odoo.define('melax_category.my_dialog_partner', function(require){"user strict";
var core = require('web.core');
var session = require('web.session');
var qweb = core.qweb;
var mixins = core.mixins;
// var Widget = require('web.Widget');
var Model = require('web.Model');
var Dialog = require('web.Dialog');
var model_category = new Model('res.partner');
// var dataset = this.dataset;
// var active_id = dataset.ids[dataset.index];
function ActionBuildTreeBuyerPartner(parent, action){
console.log(this.id)
var dialog = new Dialog(document.body, {
title: "Categories Web Acheteur",
subtitle: '<input id="search-tree-buyer" type="text">',
size: 'big',
$content: '\
<div id="tree-buyer-container"></div>\
',
});
TREE = $('#tree-buyer-container')
dialog.open();
DESTROY_TREE($('#tree-buyer-container'))
BUILD_TREE_PARTNER_MODE(model_category, $('#tree-buyer-container'))
}
function ActionBuildTreeSellerPartner(parent, action){
var dialog = new Dialog(document.body, {
title: "Categories Web Vendeur",
// subtitle: "Tree Builder Partner Sub Title!",
size: 'big',
$content: '<div id="tree-seller-container"></div>',
});
TREE = $('#tree-seller-container')
dialog.open();
// DESTROY_TREE($('#tree-seller-container'))
// BUILD_TREE_PARTNER_MODE(model_category, $('#tree-buyer-container'))
}
core.action_registry.add("build_tree_buyer_partner", ActionBuildTreeBuyerPartner);
core.action_registry.add("build_tree_seller_partner", ActionBuildTreeSellerPartner);
});
and here's my python file where for example i'm trying to pass my active_id(self.id):
#api.multi
def build_tree_buyer_partner(self):
_logger.error(self)
# My active Id
_logger.error(self.id)
return {
'type':'ir.actions.client',
'tag': 'build_tree_buyer_partner'
}
yeah, you can get the current id by using:
var dataset = this.dataset;
var active_id = dataset.ids[dataset.index];
Python
#api.model
def _get_active_id(self):
return self._context.get('active_id')
deploy_config_id = fields.Many2one('res.partner', string='Customer', required=True, default=_get_active_id)

Live update highcharts-gauge from dweet

I would like to have the gauge chart update live dweet data, which is success.
The problem is that every time a new data is pushed to the array humidityData, a new pointer is added in the gauge chart as shown here:
guage chart Though I'd like to have one live-updating-pointer instead.
Could this be done by pop() the prev data?
<script language="JavaScript">
//Array to store sensor data
var humidityData = []
<!--START-->
$(document).ready(function() {
//My Dweet thing's name
var name = 'dweetThingName'
//Humidity chart
var setupSecondChart = function() {
var chart2 = {
type: 'gauge'
};
var title = {...};
var pane = {...};
var yAxis = {...};
var tooltip = {
formatter: function () {
return '<b>' + "Humidity: " + Highcharts.numberFormat(this.y, 2) + "%";
}
};
//Series_Humidity
humiditySeries = [{
name: 'Humidity %',
data: humidityData,
tooltip: {
valueSuffix: '%'
}
}]
//Json_Humidity
var humJson = {};
humJson.chart = chart2;
humJson.title = title;
humJson.tooltip = tooltip;
humJson.xAxis = xAxis;
humJson.yAxis = yAxis;
humJson.legend = legend;
humJson.exporting = exporting;
humJson.series = humiditySeries;
humJson.plotOptions = plotOptions;
console.log("Sereies: : " +humJson)
//Container_Humidity
$('#containerHumidity').highcharts(humJson);
}
var humiditySeries = [] ....
dweetio.get_all_dweets_for(name, function(err, dweets){
for(theDweet in dweets.reverse())
{
var dweet = dweets[theDweet];
//Dweet's variables' names
val2 = dweet.content["Humidity"]
//Add the vals into created arrayDatas
humidityData.push(val2)
console.log("HumidityData: " + humidityData)
}
//Call other charts
setupSecondChart()
});
When you initialize/update your chart make sure that data array contains only one element. The dial is created for every point in this array (to visualize it on the plot).

Amcharts' stockchart : lines in javascript too simple versus old flash version

I have to update an old amcharts' stockchart in the flash version to the javascript version. I made it, but the result is too different for my client.
The flash version :
The new javascript version :
As you can see, the graph's lines on the new version are really too simple compare to the old version, there isn't enough details.
This is the code of the new graph :
var chart = new AmCharts.AmStockChart();
chart.dataSets = dataSets;
var stockPanel = new AmCharts.StockPanel();
stockPanel.showCategoryAxis = true;
stockPanel.numberFormatter = {precision:2, decimalSeparator:',', thousandsSeparator:' '};
stockPanel.percentFormatter = {precision:2, decimalSeparator:',', thousandsSeparator:' '};
var graph = new AmCharts.StockGraph();
graph.valueField = 'value';
graph.openField = 'open';
graph.closeField = 'close';
graph.comparable = true;
graph.type = 'line';
graph.minDistance = 0;
graph.noStepRisers = true;
graph.clustered = false;
stockPanel.addStockGraph(graph);
var stockLegend = new AmCharts.StockLegend();
stockLegend.markerType = 'bubble';
stockLegend.markerSize = 8;
stockLegend.periodValueText = '[[value.close]]';
stockLegend.valueTextComparing = '[[value]] | [[percents.value]]%';
stockLegend.periodValueTextComparing = '[[value.close]] | [[percents.value.close]]%';
stockLegend.horizontalGap = 1;
stockLegend.spacing = 100;
stockPanel.stockLegend = stockLegend;
chart.panels = [ stockPanel ];
var categoryAxesSettings = new AmCharts.CategoryAxesSettings();
chart.categoryAxesSettings = categoryAxesSettings;
var scrollbarSettings = new AmCharts.ChartScrollbarSettings();
scrollbarSettings.color = '#000';
scrollbarSettings.gridColor = '#fff';
scrollbarSettings.backgroundColor = '#fff';
scrollbarSettings.gridColor = '#fff';
scrollbarSettings.graphFillColor = '#F5F5F5'; //jsonData.funds.color;
scrollbarSettings.selectedGraphFillColor = '#CCDDE9';
scrollbarSettings.selectedBackgroundColor = '#fff'
scrollbarSettings.graph = graph;
scrollbarSettings.graphType = 'line';
scrollbarSettings.usePeriod = "MM";
chart.chartScrollbarSettings = scrollbarSettings;
var periodSelector = new AmCharts.PeriodSelector();
periodSelector.position = 'top';
periodSelector.fromText = '';
periodSelector.toText = ' - ';
periodSelector.periodsText = '';
periodSelector.dateFormat = 'DD/MM/YYYY';
periodSelector.periods = [
{ period: 'MM', count: 1, label: '1M' },
{ period: 'MM', count: 3, label: '3M' },
{ period: 'YYYY', count: 1, label: '1Y' },
{ period: 'YYYY', count: 3, label: '3Y' },
{ period: 'YYYY', count: 5, label: '5Y' },
{ period: 'YTD', label: 'YTD' },
{ period: 'MAX', label: 'MAX' }
];
chart.periodSelector = periodSelector;
chart.write('fund_historic');
Which parameter do I have to add or change ?
Thanks
OK I found it.
When I compared the flash settings file, I see the max_series parameter fixed to 300, the double of the default value of the javascript version.
So this is the solution :
var categoryAxesSettings = new AmCharts.CategoryAxesSettings();
categoryAxesSettings.maxSeries = 300;
chart.categoryAxesSettings = categoryAxesSettings;

Categories