Is it possible to create a "Print all" button for Highcharts, where more that one chart is printed?
I know that exporting multiple charts is possible, as demonstrated in the jFiddle: http://jsfiddle.net/highcharts/gd7bB/1/ but I'm not sure Highcharts allows a similar method with printing.
The exportChart method accepts parameters so you can send it more than one chart. However, the print method does not accept any parameters. So, to print you have to specify each chart separately like chart1.print(); and chart2.print(); which prints them each separately.
There are two workarounds:
Printing the whole page without using Highcharts printing. Here is an example.
You could export both of the charts to a pdf file and then print form there. Here is an example on how to export multiple charts to one pdf.
Here is an alternative solution that does the printing directly. It's based on the chart.print() function, but it works on multiple charts.
See the demo here: http://jsfiddle.net/jim31415/q5Rzu/150/
//--------------------------------------------------------------------
$("#print").click(function () {
printCharts([chart1, chart2, chart3]);
});
//--------------------------------------------------------------------
function printCharts(charts) {
var origDisplay = [],
origParent = [],
body = document.body,
childNodes = body.childNodes;
// hide all body content
Highcharts.each(childNodes, function (node, i) {
if (node.nodeType === 1) {
origDisplay[i] = node.style.display;
node.style.display = "none";
}
});
// put the charts back in
$.each(charts, function (i, chart) {
origParent[i] = chart.container.parentNode;
body.appendChild(chart.container);
});
// print
window.print();
// allow the browser to prepare before reverting
setTimeout(function () {
// put the charts back in
$.each(charts, function (i, chart) {
origParent[i].appendChild(chart.container);
});
// restore all body content
Highcharts.each(childNodes, function (node, i) {
if (node.nodeType === 1) {
node.style.display = origDisplay[i];
}
});
}, 500);
}
});
Related
I have two functions displayDataGraph and displayDataTable that displays data as a table and graph. I have a button that toggles between graph and table display.The graph is the default view and the switch to the table view happens with the toggle button.
Each page has a previous and next button that takes user to next 20 set of data or previous 20 set of data.
The expected behavior is that while viewing the data as a table, the next page will also display the data as table, and that while viewing the data as a graph, the next or previous page will also display the data as a graph.
The current behavior is that while viewing as a graph and I click on view as table button, the table is displayed. But when I click on next or previous, it takes me back to the graph instead of the next page of the table format. This doesn't happen with the graph. When I click on the next button while viewing as a graph, it takes me to the next page as the graph (perhaps because it's the default? )
Which means the toggling works just once, and when I click next or previous, it just goes back to the original/default view/state
Any idea why this behavior is happening or how it can be fixed?
My code is quite long and i don't know how much help sharing snippets would be, but here's the toggling function:
const toggleButton = () => {
const data = document.querySelectorAll("button.data");
const dataTable = document.querySelector(".dataTable");
const dataGraph = document.querySelector(".dataGraph");
if (dataTable && dataGraph) {
if (dataTable.style.display === "block") {
dataTable.style.display = "none";
dataGraph.style.display = "block";
data.forEach(
(element) => (element.innerText = "Display Data as Table")
);
} else {
dataGraph.style.display = "none";
dataTable.style.display = "block";
data.forEach(
(element) => (element.innerText = "Display Data as Chart")
);
}
}
};
I have other relevant functions such as
const displayDataTable
const displayGraphTable
const paginateData
You need to be able to save the desired view state across page refreshes. As Lauren Groh mentions, can you save it in Session Storage?
When your page loads it sounds like the css display property for the dataTable is being set to none and the css display property on the dataGraph is being set to block. That's fine but it's hardcoded into the pageload. Your javascript toggle button changes the css style properly but when you refresh the page, it loads everything up from the begining.
To solve it, you need to add some check at page load that looks at Session Storage before deciding whether the dataTable or dataGraph should be display none or block. Here is a an idea:
const data = document.querySelectorAll("button.data");
const dataTable = document.querySelector(".dataTable");
const dataGraph = document.querySelector(".dataGraph");
//extract the functionality:
const loadGraphView = () => {
dataTable.style.display = "none";
dataGraph.style.display = "block";
data.forEach((element) => (element.innerText = "Display Data as Table"));
// Save data to sessionStorage
sessionStorage.setItem("dataViewType", "graph");
};
const loadTableView = () => {
dataGraph.style.display = "none";
dataTable.style.display = "block";
data.forEach((element) => (element.innerText = "Display Data as Chart"));
sessionStorage.setItem("dataViewType", "table");
};
const toggleView = () => {
const viewType = sessionStorage.getItem("dataViewType");
if (viewType) {
if (viewType === "table") {
loadTableView();
} else if (viewType === "graph") {
loadGraphView();
}
}
};
//At some point when you load the page, call toggle view
//Then any time you click the button, have it call toggle view.
toggleView();
Saving the state to Session Storage will allow you to keep track of the desired view until the person closes their browser. If you want to mantain state after the browser closes, you can do something similar with Local Storage instead.
localStorage.setItem('myCat', 'Tom');
const cat = localStorage.getItem('myCat');
There are probably nicer ways to implement this code. I just tried to put it down in a way that fits with what you showed us and should allow you to take it from there and make it prettier. :-)
If there's a refresh involved, maybe the toggle is defaulting, as you said. You could store the desired state in SessionStorage so it sticks after refresh.
Iv set up a custom onClick event to hide columns in a heat map using a "fake group". Everything works, as long as you wait till the animations finish before clicking on another year form the yearSlicer. Clicking before it is finished loading will cause unwanted results.
fake group:
function filter_bins(source_group, f) {
return {
all: function () {
return source_group.all().filter(function (d) {
return f(d.value);
});
}
};
}
var filtered_years_group = filter_bins(FTEMonthGroup, function (d) {
return yearsHidden.length == 0 ? !yearsHidden.includes(d.Year) : yearsHidden.includes(d.Year);
});
yearSlicer onClick:
yearSlicer.on("renderlet.somename", function (chart) {
chart.selectAll('rect').on("click", function (d) {
hideYear(d.key);
return chart._onClick(d)
});
});
setting years to hide:
var yearsHidden = [];
var hideYear = function (year) {
var index = yearsHidden.indexOf(year);
if (index === -1) {
yearsHidden.push(year);
} else {
yearsHidden.splice(index, 1);
}
heatMap.redraw();
}
https://jsfiddle.net/_M_M_/fLvugo3h/
I observed that sometimes the heatmap data would not be filtered by the current selection of years, causing grey cells to appear. Hope this is the same problem you are describing!
I'm not sure what is going wrong here, but I noticed that yearsHidden is the same as yearSlicer.filters() - you are duplicating the toggle behavior that the chart already has.
It's always nice to fix a bug by deleting code. I found that I could fix it by changing the filter_bins function to
var filtered_years_group = filter_bins(FTEMonthGroup, function(d) {
var years = yearSlicer.filters();
return years.length ? years.includes(d.Year) : true;
});
and then remove everything to do with yearsHidden, as well as the click event handler.
https://jsfiddle.net/gordonwoodhull/8npv5zhq/19/
Without going into a deep debugging session, my guess is that they got out of synch because a lot of the click handlers for dc.js are asynchronous. So your handler could get called at different times from the dc.js filtering code.
There are two dygraphs on my page. On regular intervals new time series data is read from source and written to local variables, then the graphs are updated using the following commands:
vm.graph1.updateOptions(
{ 'file': customData1, 'labels': vm.labels1 });
vm.graph2.updateOptions(
{ 'file': customData2, 'labels': vm.labels2 });
This works perfectly fine, the graphs show the live trend as expected.
However, when I try to synchronize the two graphs by including the syncrhonizer.js and using the following command:
vm.graphSync = Dygraph.synchronize(vm.graph1, vm.graph2);
Then the "live updates" stop working. In other words, the graph doesn't display any of the new incoming values (it just displays the same static time span). The syncronization works fine, both for selection and zoom.
The data is still getting updated, but now I have to double click on the graph (or manually pan) in order to see the most recent data.
Anyone have any ideas or working solutions for syncrhronizing live trends using Dygraphs?
You'll need to explicitly set the dateWindow after updating the data:
vm.graph1.updateOptions(
{ 'file': customData1, 'labels': vm.labels1 });
vm.graph2.updateOptions(
{ 'file': customData2, 'labels': vm.labels2 });
vm.graph1.updateOptions({
dateWindow: vm.graph1.xAxisExtremes()
});
// ^^^ this will synchronize the other charts, too
The fact that you have to do this could be considered a bug with synchronizer. Feel free to file an issue against dygraphs, preferably with a link to a repro via dygraphs.com/fiddle.
This is a working solution, however, I had to change the "synchronizer.js" from dygraphs, so comments and other suggestions are still welcome.
In attachZoomHandlers function in synchronizer.js, I made this change:
for (var j = 0; j < gs.length; j++) {
if (gs[j] == me) continue;
//added if/else block, the original code was the stuff inside else block
if (me.ignoreXrangeSync && me.ignoreXrangeSync === true) {
//if ignoreXrangeSync flag is set, only update y-range
gs[j].updateOptions( {
valueRange: yrange
});
}
else {
//if ignoreXrangeSync flag is NOT set, run original code (so manual zoom works)
gs[j].updateOptions( {
dateWindow: range,
valueRange: yrange
});
}
}
And in my original code, I made this change.
vm.graph1.ignoreXrangeSync = vm.graph2.ignoreXrangeSync = true;
vm.graph1.updateOptions(
{ 'file': customData1, 'labels': vm.labels1 });
vm.graph2.updateOptions(
{ 'file': customData2, 'labels': vm.labels2 });
vm.graph1.ignoreXrangeSync = vm.graph2.ignoreXrangeSync = false;
Also, I needed to add these zoom callbacks (one for each graph) for my graph for live trending to start when double clicking after having manually zoomed in the graph(s).
var graph1ZoomCallback = function () { //callback for graph1
if(!vm.graph1.isZoomed()) { //if graph1 has been double clicked
vm.graph2.ignoreXrangeSync = true; //note, graph2
vm.graph2.resetZoom();
vm.graph2.ignoreXrangeSync = false;
}
}
I have created a single page for all my reports and I am loading different versions of those reports (line, pie, chart, graph, etc) with a toolbar I made. All is working well there, except on the non-table type charts (line,pie,bar,etc). When those get rendered, I found that the text in the legends and series become blurry and through some research here and other places found that they are converted to images, which are then getting resized on me though a css class that is auto generated.
Firstly, what i'm trying to do:
I want to remove this class from the image that is generated at the time it is loaded. If i turn off async rendering on my report
AsyncRendering="false"
Along with this bit of jquery (targeting the div that contains the reportviewer):
$(document).ready(function () {
$('#reportDiv img').removeAttr('class');
});
Then the result is as expected. The image is not scaled and all is well. The problem, however, is that some of these reports may be quite large, resulting in the user not having any visual feedback of whether or not something is happening. I would like to continue using async rendering, so I started to look at the reportviewer javascript api.
Sys.Application.add_load(function () {
var reportViewer = $find("ctl00_mainContentPlaceHolder_ReportViewer1");
reportViewer.add_propertyChanged(viewerPropertyChanged);
});
function viewerPropertyChanged(sender, e) {
var viewer = $find("ctl00_mainContentPlaceHolder_ReportViewer1");
if (e.get_propertyName() === "isLoading") {
var button = document.getElementById("ctl00_mainContentPlaceHolder_ctlReportParamModuleId1_btnRunReport");
button.disabled = viewer.get_isLoading();
}
else {
if ($find("ctl00_mainContentPlaceHolder_ReportViewer1").get_reportAreaContent() == Microsoft.Reporting.WebFormsClient.ReportAreaContent.ReportPage) {
alert("here");
}
}
}
The first portion (isLoading) works as expected disabling the button. However immediately upon load I get
Object doesn't support property or method 'get_reportAreaContent'
Am I missing something obvious? These are the links from msdn that I used for reference:
reportviewer isLoading
reportviewer ReportAreaContentType
Bar graphs, Line graphs, pie charts, etc. are rendered as images. The images get re-sized based on the size of the report viewer control. Instead of using AsyncRendering="false", I created this javascript workaround and it has solved my problem.
var app = Sys.Application;
app.add_init(ApplicationInit);
function ApplicationInit(sender) {
var prm = Sys.WebForms.PageRequestManager.getInstance();
if (!prm.get_isInAsyncPostBack()) {
prm.add_endRequest(EndRequest)
}
}
function EndRequest(sender, args) {
var reportViewerControlId = "ReportViewer1";
if (sender._postBackControlClientIDs[0].indexOf(reportViewerControlId) >= 0) {
var reportViewerControlContainer = "reportViewerContainer"; // Id of <DIV>
var renderedReportImage = $("#" + reportViewerControlContainer + " img");
renderedReportImage.removeAttr("style").removeAttr("class");
var styleAttr = renderedReportImage.attr("style");
var classAttr = renderedReportImage.attr("class");
if (typeof styleAttr === 'undefined') {
console.log("Successfully removed the style attribute from the rendered report image!");
}
if (typeof classAttr === 'undefined') {
console.log("Successfully removed the class attribute from the rendered report image!");
}
}
}
Basically, I am listening to the endRequest of the PageRequestManager for my ReportViewerControl's ID, then simply removing the style and class attributes from the image to display it unmodified.
I use javascript to display a series of charts (graphs) one under the other. The Javascript script requests data from a PHP script then draws the charts and displays it. It doesn't take much time to fetch data for one chart and diplay it but with many charts the time accumulates which is boring. I would like to display the first chart when it is ready and then the second chart etc. so user sees how those get dynamically appended on the page making the page longer and longer.
Basically here is what I have:
<script>
var i=0;
$.getJSON('stats.php', function (data) {
for (chartData in data) {
i++;
var chart = new AmStockChart();
dataSet.dataProvider = chartData;
// etc. etc.
$('#stats').append('div id="chartdiv' + i + '"></div>');
chart.write("chartdiv" + i);
}
});
</script>
<div id="stats"></div>
That produces about 10 graphs one under the other but the problem is that shows empty screen for about 3-5 seconds and then displays all the graphs at once. If I limit it to diplay only one graph it shows up much faster. It would be nice to show those graphs gradually one by one when each graph is ready.
UPD: Here is the jsfiddle I put test data and cycled through one graph 20 times.
Some thing along the lines of this should help.....
var i = 0;
$.getJSON('stats.php', function (data) {
for (chartData in data) {
i++;
setTimeout(function(index,d1){
return function() {
var chart = new AmStockChart();
dataSet.dataProvider = data[d1];
// etc. etc.
$('#stats').append('div id="chartdiv' + index + '"></div>');
chart.write("chartdiv" + index);
}
}(i,chartData), 3000*i);
}
});
Here is the demo