How to make a page resize without resizing? - javascript

How do I make a page resize without resizing it?
I have a Javascript which runs to get data from a text file followed by plotting a graph on Amcharts. The problem is that some axis components of the chart are missing when it is first plotted and still missing when I attempt to refresh (F5).
The missing components all come out when I resize the browser, so I am wondering what is it that the resizing does and how do I mimic a resize after the chart has been drawn without actually resizing?
The full html version of the source is right here.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>amCharts Example</title>
<link rel="stylesheet" href="style.css" type="text/css">
<script src="javascript/amcharts.js" type="text/javascript"></script>
<script type="text/javascript">
var chart;
var chartData = [];
// declaring variables
var dataProvider;
// this method called after all page contents are loaded
var srcFrame;
window.onload = function() {
//generateChartData();
//createChart();
loadOuter('data.txt');
//loadOuter('Test.txt');
//loadCSV('data.txt');
}
//External content into a layer
function loadOuter(doc) {
srcFrame = document.getElementById("hiddenContent");
srcFrame.src = doc;
// workaround for missing onLoad event in IFRAME for NN6
if (!srcFrame.onload) {
if (srcFrame.contentDocument){
srcContent=srcFrame.contentDocument.getElementsByTagName("BODY")[0].innerHTML;
}
else if (srcFrame.contentWindow){
srcContent=srcFrame.contentWindow.document.body.innerHTML;
}
srcContent = srcContent.substring(5,srcContent.length-6)
parseCSV(srcContent);
//setTimeout("transferHTML()", 1000)
}
}
function parseCSV(data){
//replace UNIX new lines
data = data.replace (/\r\n/g, "\n");
//replace MAC new lines
data = data.replace (/\r/g, "\n");
//split into rows
var rows = data.split("\n");
// create array which will hold our data:
dataProvider = [];
// loop through all rows
for (var i = 0; i < rows.length; i++){
// this line helps to skip empty rows
if (rows[i]) {
// our columns are separated by comma
var column = rows[i].split(",");
// column is array now
// first item is date
var date = column[0];
var myDate= new Date();
var dateparse = date.split("-");
myDate.setFullYear(dateparse[0],dateparse[1],dateparse[2]);
//alert(myDate);
// second item is value of the second column
var value1 = column[1];
// third item is value of the fird column
var value2 = column[2];
// create object which contains all these items:
chartData.push({
date: myDate,
visits: value1,
hits: value2,
});
//var dataObject = {date:date, value1:value1, value2:value2};
// add object to dataProvider array
//dataProvider.push(dataObject);
}
}
// set data provider to the chart
chart.dataProvider = chartData;
// this will force chart to rebuild using new data
chart.validateData();
}
// generate some random data, quite different range
function generateChartData() {
var firstDate = new Date();
firstDate.setDate(firstDate.getDate() - 50);
//alert(firstDate);
//alert(firstDate.getDate());
for (var i = 0; i < 50; i++) {
var newDate = new Date(firstDate);
newDate.setDate(newDate.getDate() + i);
//alert(newDate);
var visits = Math.round(Math.random() * 40) + 100;
var hits = Math.round(Math.random() * 80) + 500;
chartData.push({
date: newDate,
visits: visits,
hits: hits,
});
}
}
//function createChart(){
AmCharts.ready(function () {
//loadOuter('data.txt');
// generate some random data first
//generateChartData();
// SERIAL CHART
chart = new AmCharts.AmSerialChart();
chart.pathToImages = "amcharts/images/";
chart.zoomOutButton = {
backgroundColor: '#000000',
backgroundAlpha: 0.15
};
chart.dataProvider = chartData;
chart.categoryField = "date";
// listen for "dataUpdated" event (fired when chart is inited) and call zoomChart method when it happens
chart.addListener("dataUpdated", zoomChart);
// AXES
// category
var categoryAxis = chart.categoryAxis;
categoryAxis.parseDates = true; // as our data is date-based, we set parseDates to true
categoryAxis.minPeriod = "DD"; // our data is daily, so we set minPeriod to DD
categoryAxis.dashLength = 2;
categoryAxis.gridAlpha = 0.15;
categoryAxis.axisColor = "#DADADA";
// first value axis (on the left)
var valueAxis1 = new AmCharts.ValueAxis();
valueAxis1.axisColor = "#FF6600";
valueAxis1.axisThickness = 2;
valueAxis1.gridAlpha = 0;
chart.addValueAxis(valueAxis1);
// second value axis (on the right)
var valueAxis2 = new AmCharts.ValueAxis();
valueAxis2.position = "right"; // this line makes the axis to appear on the right
valueAxis2.axisColor = "#FCD202";
valueAxis2.gridAlpha = 0;
valueAxis2.axisThickness = 2;
chart.addValueAxis(valueAxis2);
// GRAPHS
// first graph
var graph1 = new AmCharts.AmGraph();
graph1.valueAxis = valueAxis1; // we have to indicate which value axis should be used
graph1.title = "red line";
graph1.valueField = "visits";
graph1.bullet = "round";
graph1.hideBulletsCount = 30;
chart.addGraph(graph1);
// second graph
var graph2 = new AmCharts.AmGraph();
graph2.valueAxis = valueAxis2; // we have to indicate which value axis should be used
graph2.title = "yellow line";
graph2.valueField = "hits";
graph2.bullet = "square";
graph2.hideBulletsCount = 30;
chart.addGraph(graph2);
// CURSOR
var chartCursor = new AmCharts.ChartCursor();
chartCursor.cursorPosition = "mouse";
chart.addChartCursor(chartCursor);
// SCROLLBAR
var chartScrollbar = new AmCharts.ChartScrollbar();
chart.addChartScrollbar(chartScrollbar);
// LEGEND
var legend = new AmCharts.AmLegend();
legend.marginLeft = 110;
chart.addLegend(legend);
// WRITE
chart.write("chartdiv");
});
// this method is called when chart is first inited as we listen for "dataUpdated" event
function zoomChart() {
// different zoom methods can be used - zoomToIndexes, zoomToDates, zoomToCategoryValues
chart.zoomToIndexes(10, 20);
//chart.validateData();
//createChart();
}
</script>
<div id="outerDisplay"></div>
<iframe id="hiddenContent" width="200" height="200" style="position:absolute;visibility:hidden;" ></iframe>
<div id="chartdiv" style="width:600px; height:400px; background-color:#FFFFFF"></div>
</body>
</html>
Anyone has any idea on how to troubleshoot it to make it display on the first load instead of having to resize it?

Did you try calling chart.validateNow()? See Amcharts reference.
Chart will be redrawn, useful when a property changes.

Related

Switching to JSON data in AmCharts not working

New user to Amcharts (and programming!) and was trying to use one of their examples (https://codepen.io/team/amcharts/pen/gOpWroQ),
but when I pull the data from and external JSON file, it doesn't work properly. The JSON structure and format is correct and I literally copied the data from the JSON file into the var data [] and it works fine.
The pen is here: https://codepen.io/moneycarlo/pen/zYKdyGz (however I can't host a json file).
Line 10 is where I'm loading the JSON data, but if I remove the var data info and uncomment out line 10, the result doesn't draw 4 lines. Instead, it's 1 line with multiple stacked points on it for each date.
I'm guessing it's something in the pre-processor. I was under the impression that when you loaded from external data like the JSON it would automatically be assigned to the data property and those functions would work the same.
What am I missing?
am4core.useTheme(am4themes_animated);
// Source data
var data = [
{"date":"2019-07-05","domain":"aol.com","hits":"119489"},{"date":"2019-07-05","domain":"gmail.com","hits":"295834"},{"date":"2019-07-05","domain":"hotmail.com","hits":"8000"},{"date":"2019-07-05","domain":"yahoo.com","hits":"324263"},{"date":"2019-07-06","domain":"aol.com","hits":"195042"},{"date":"2019-07-06","domain":"gmail.com","hits":"258402"},{"date":"2019-07-06","domain":"hotmail.com","hits":"100000"},{"date":"2019-07-06","domain":"yahoo.com","hits":"427865"}
];
// Create chart instance
var chart = am4core.create("chartdiv", am4charts.XYChart);
//chart.dataSource.url = "data_1.php";
// Create axes
var dateAxis = chart.xAxes.push(new am4charts.DateAxis());
dateAxis.renderer.grid.template.location = 0;
dateAxis.renderer.minGridDistance = 30;
var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
chart.colors.list = [
am4core.color("red"),
am4core.color("blue"),
am4core.color("green")
];
// Create series
function createSeries(field, name, id) {
var series = chart.series.push(new am4charts.LineSeries());
series.dataFields.valueY = "hits";
series.dataFields.dateX = "date";
series.name = name;
series.tooltipText = "{dateX}: [b]{valueY}[/]";
series.strokeWidth = 2;
var bullet = series.bullets.push(new am4charts.CircleBullet());
bullet.circle.stroke = am4core.color("#fff");
bullet.circle.strokeWidth = 2;
// Add data pre-processor
series.data = data;
series.events.on("beforedatavalidated", function(ev) {
var source = ev.target.data;
var data = [];
for(var i = 0; i < source.length; i++) {
var row = source[i];
if (row.domain == id) {
data.push(row);
}
}
ev.target.data = data;
});
return series;
}
createSeries("hits", "Yahoo", "yahoo.com");
createSeries("hits", "Hotmail", "hotmail.com");
createSeries("hits", "Gmail", "gmail.com");
chart.legend = new am4charts.Legend();
chart.cursor = new am4charts.XYCursor();
dataSource assigns the data into the data array at the chart object, not at the series like the rest of your code does. You'll need to hook into the dataSource's parseended event and process each series with the existing beforedatavalidated code. You'll also want to keep track of the id value in your createSeries method as it won't be available in the parseended event:
chart.dataSource.events.on('parseended', function(ev){
// process parsed data into each series' data array
ev.target.component.series.each(function(series) {
var source = ev.target.data;
var data = [];
for(var i = 0; i < source.length; i++) {
var row = source[i];
if (row.domain == series.id) {
data.push(row);
}
}
series.data = data;
});
// clear out data array so that it isn't re-assigned to the chart
// data array
ev.target.data = [];
});
// ...
function createSeries(field, name, id) {
var series = chart.series.push(new am4charts.LineSeries());
series.dataFields.valueY = "hits";
series.dataFields.dateX = "date";
series.id = id; //store id for later
series.name = name;
series.tooltipText = "{dateX}: [b]{valueY}[/]";
series.strokeWidth = 2;
var bullet = series.bullets.push(new am4charts.CircleBullet());
bullet.circle.stroke = am4core.color("#fff");
bullet.circle.strokeWidth = 2;
return series;
}
Codepen

AmCharts 4 - multiple datasets

please, I'm trying to create with AmCharts 4 chart with multiple data sets. In previous version it is done this way: https://www.amcharts.com/demos/multiple-data-sets/
but I can't find anything how to make it in 4th version.
I use the code from their website.
Everything is ok, next chart I want to add in section starting with comment add ATR -> I need to show it under the original chart.
Thank you very much for any help.
Have a nice day!
var chart = am4core.create("candlestick-chart", am4charts.XYChart);
chart.paddingRight = 20;
// chart.dateFormatter.inputDateFormat = "YYYY-MM-dd";
chart.dateFormatter.inputDateFormat = "x";
var dateAxis = chart.xAxes.push(new am4charts.DateAxis());
dateAxis.renderer.grid.template.location = 0;
var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.tooltip.disabled = true;
/*
add default series
*/
var series = chart.series.push(new am4charts.CandlestickSeries());
series.dataFields.dateX = "date";
series.dataFields.valueY = "close";
series.dataFields.openValueY = "open";
series.dataFields.lowValueY = "low";
series.dataFields.highValueY = "high";
series.dataFields.atr = "atr";
series.dataFields.average_atr = "average_atr";
series.simplifiedProcessing = true;
series.tooltipText = "Open:${openValueY.value}\nLow:${lowValueY.value}\nHigh:${highValueY.value}\nClose:${valueY.value}\nATR:{atr.formatNumber('#.000')}\nAverage ATR:{average_atr.formatNumber('#.000')}";
series.riseFromPreviousState.properties.fillOpacity = 1;
series.dropFromPreviousState.properties.fillOpacity = 0;
chart.cursor = new am4charts.XYCursor();
/*
add scrollbar
*/
// a separate series for scrollbar
var scrollbarSeries = chart.series.push(new am4charts.LineSeries());
scrollbarSeries.dataFields.dateX = "date";
scrollbarSeries.dataFields.valueY = "close";
// need to set on default state, as initially series is "show"
scrollbarSeries.defaultState.properties.visible = false;
// hide from legend too (in case there is one)
scrollbarSeries.hiddenInLegend = true;
scrollbarSeries.fillOpacity = 0.5;
scrollbarSeries.strokeOpacity = 0.5;
var scrollbarX = new am4charts.XYChartScrollbar();
scrollbarX.series.push(scrollbarSeries);
chart.scrollbarX = scrollbarX;
/*
add ATR
-> I need to show it under the original chart
*/
var lineSeriesATR = chart.series.push(new am4charts.LineSeries());
lineSeriesATR.dataFields.dateX = "date";
lineSeriesATR.dataFields.valueY = "atr";
lineSeriesATR.defaultState.properties.visible = false;
// hide from legend too (in case there is one)
lineSeriesATR.hiddenInLegend = true;
lineSeriesATR.fillOpacity = 0.5;
lineSeriesATR.strokeOpacity = 0.5;
chart.data = {!! json_encode($candles) !!};
AmCharts 4 does not have the stock chart implemented yet. You can approximate panels and synced cursor and zoom by using the chart's containers to create additional charts and using the API to make the cursor and scrollbar affect all the charts.
You can create each chart and push them onto the chart container like so:
var container = am4core.create("chartdiv", am4core.Container); //create the container
container.width = am4core.percent(100); //set dimensions and layout
container.height = am4core.percent(100);
container.layout = "vertical";
// ... for each chart
var chart = container.createChild(am4charts.XYChart);
// ..set up as usual
While setting up each chart, you'll need to set up events to sync up zoom events:
// whenever any of the charts is zoomed, we should zoom all other charts
dateAxis.events.on("selectionextremeschanged", function (event) {
syncDateAxes(event.target);
})
// ...
function syncDateAxes(syncWithAxis) {
for (var i = 0; i < charts.length; i++) {
var chart = charts[i];
var dateAxis = chart.xAxes.getIndex(0);
if (dateAxis != syncWithAxis) {
dateAxis.events.disableType("selectionextremeschanged");
dateAxis.start = syncWithAxis.start;
dateAxis.end = syncWithAxis.end;
dateAxis.events.enableType("selectionextremeschanged");
}
}
}
You'll also want to set up each chart's cursor and sync each of them up:
function initCursorListeners() {
cursorShowDisposers = [];
for (var i = 0; i < charts.length; i++) {
var chart = charts[i];
var cursor = chart.cursor;
cursor.interactionsEnabled = true;
cursorShowDisposers.push(cursor.events.on("shown", function (event) {
handleShowCursor(event.target);
}));
}
}
var shownCursorChangeDisposer;
var shownCursorZoomStartedDisposer;
var shownCursorZoomEndedDisposer;
function handleShowCursor(shownCursor) {
// disable mouse for all other cursors
for (var i = 0; i < charts.length; i++) {
var chart = charts[i];
var cursor = chart.cursor;
if (cursor != shownCursor) {
cursor.interactionsEnabled = false;
}
// remove show listener
cursorShowDisposers[i].dispose();
}
// add change disposer to the hovered chart cursor
shownCursorChangeDisposer = shownCursor.lineX.events.on("positionchanged", function (event) {
syncCursors(shownCursor);
});
shownCursorZoomStartedDisposer = shownCursor.events.on("zoomstarted", function (event) {
for (var i = 0; i < charts.length; i++) {
var chart = charts[i];
var cursor = chart.cursor;
if (cursor != event.target) {
var point = { x: event.target.point.x, y: 0 };
cursor.triggerDown(point);
}
}
});
shownCursorZoomEndedDisposer = shownCursor.events.on("zoomended", function (event) {
for (var i = 0; i < charts.length; i++) {
var chart = charts[i];
var cursor = chart.cursor;
if (cursor != event.target) {
var point = { x: event.target.point.x, y: 0 };
cursor.triggerUp(point);
}
}
});
shownCursor.events.once("hidden", function (event) {
shownCursorChangeDisposer.dispose();
shownCursorZoomStartedDisposer.dispose();
shownCursorZoomEndedDisposer.dispose();
for (var i = 0; i < charts.length; i++) {
var chart = charts[i];
var cursor = chart.cursor;
cursor.hide(0);
cursorShowDisposers[i].dispose();
}
initCursorListeners();
});
}
function syncCursors(syncWithCursor) {
for (var i = 0; i < charts.length; i++) {
var chart = charts[i];
var cursor = chart.cursor;
var point = { x: syncWithCursor.point.x, y: 0 };
if (cursor != syncWithCursor) {
cursor.triggerMove(point);
}
}
}
There are other UI tweaks you can make like disabling the zoom out button on other charts except the top-most one as well as showing the date axis labels on just the last chart.
Here's a full demo on how to do all of this. This is also included in the examples folder in the AmCharts zip file.
If you're using Stock Chart, you might want to hold off.
Stock chart is not yet available for V4. We're planning it for the
second part of 2018. No exact ETA, yet. Sorry.
https://www.amcharts.com/docs/v4/getting-started/migrating-from-v3/#No_Stock_chart_yet

Getting the same element from database to AmCharts using Primefaces and Javascript

Good day.
Task:
Output diagram using data from DB via AmCharts.
Backend: Primefaces + MySQL
I transfer data to AmCharts using this code:
<script type="text/javascript">
var chart;
var data = [];
for (var i = 0; i < '${countryMB.list.size()}'; i++) {
data.push(
{
country: '${countryMB.list.get(i).getName()}',
gdp: '${countryMB.list.get(i).getGdp()}'
}
);
}
var chartData = data;
AmCharts.ready(function () {
// SERIAL CHART
chart = new AmCharts.AmSerialChart();
chart.dataProvider = chartData;
chart.categoryField = "country";
chart.startDuration = 1;
// AXES
// category
var categoryAxis = chart.categoryAxis;
categoryAxis.labelRotation = 90;
categoryAxis.gridPosition = "start";
// GRAPH
var graph = new AmCharts.AmGraph();
graph.valueField = "gdp";
graph.balloonText = "[[category]]: [[value]]";
graph.type = "column";
graph.lineAlpha = 0;
graph.fillAlphas = 0.8;
chart.addGraph(graph);
chart.write("chartdiv");
});
</script>
countryMB - primefaces component and list - method that gets all elements from database.
And as a result I have data only for one element, that repeats many times.
Like this:
I don't understand the reason.
Thanks for Your help.

Display date in x Axis value using amCharts

I've a AmSerialChart with three AmGraph on whcih I've formatted the balloonText like this:
grp.balloonText = "<small><b>Date: [[category]]</b></small><br>[[value]]";
The problem with the category (x value) is that is also displayed in the balloonText with the following format: "MMM DD, YYYY". How can I display this date in another way?
I've checked dateFormats in the categoryaxis and dataDateFormat but that only changes the bottom value.
Here's the full code so far:
<script src="amcharts/amcharts.js" type="text/javascript"></script>
<script src="amcharts/serial.js" type="text/javascript"></script>
<script type="text/javascript">
var chart;
var chartData = <% properties.get("jsonData") %>;
var chartTitles = <% properties.get("jsonTitles") %>;
AmCharts.ready(function () {
// SERIAL CHART
chart = new AmCharts.AmSerialChart();
chart.pathToImages = "amcharts/images/";
chart.dataProvider = chartData;
chart.categoryField = "date";
chart.dataDateFormat = [{period:'fff',format:'JJ:NN:SS'},{period:'ss',format:'JJ:NN:SS'},{period:'mm',format:'JJ:NN:SS'},{period:'hh',format:'JJ:NN:SS'},{period:'DD',format:'JJ:NN:SS'},{period:'WW',format:'JJ:NN:SS'},{period:'MM',format:'JJ:NN:SS'},{period:'YYYY',format:'JJ:NN:SS'}];
// listen for "dataUpdated" event (fired when chart is inited) and call zoomChart method when it happens
// chart.addListener("dataUpdated", zoomChart);
// AXES
// category
var categoryAxis = chart.categoryAxis;
categoryAxis.parseDates = true; // as our data is date-based, we set parseDates to true
categoryAxis.minPeriod = "ss";
categoryAxis.minorGridEnabled = true;
categoryAxis.axisColor = "#DADADA";
// categoryAxis.dateFormats = [{period:'fff',format:'JJ:NN:SS'},{period:'ss',format:'JJ:NN:SS'},{period:'mm',format:'JJ:NN:SS'},{period:'hh',format:'JJ:NN:SS'},{period:'DD',format:'JJ:NN:SS'},{period:'WW',format:'JJ:NN:SS'},{period:'MM',format:'JJ:NN:SS'},{period:'YYYY',format:'JJ:NN:SS'}];
var vAxis = new AmCharts.ValueAxis();
chart.addValueAxis(vAxis);
for (var i = 0; i < chartTitles.length; i++) {
var grp = new AmCharts.AmGraph();
grp.valueField = "d"+i;
grp.title = chartTitles[i];
grp.type = "line";
grp.valueAxis = vAxis; // indicate which axis should be used
grp.lineThickness = 1;
grp.bullet = "round";
grp.labelPosition = "right";
grp.balloonText = "<small><b>Date: [[category]]</b></small><br>[[value]]";
grp.balloonText = "[[value]], [[description]], [[percents]], [[open]], [[total]], [[category]]";
grp.showBalloon = true;
grp.bulletSize = 1;
grp.bulletBorderThickness = 6;
grp.dashLengthField = "dashLength";
chart.addGraph(grp);
}
// SCROLLBAR
var chartScrollbar = new AmCharts.ChartScrollbar();
chart.addChartScrollbar(chartScrollbar);
// LEGEND
var legend = new AmCharts.AmLegend();
legend.marginLeft = 180;
legend.useGraphSettings = true;
chart.addLegend(legend);
// WRITE
chart.write("chartdiv");
});
</script>
<div id="chartdiv" style="width: 100%; height: 360px;"></div>
Good questions, which helped me to find out that I have a missing property in the docs. In case you don't use ChartCursor, you should use chart.balloonDateFormat property to format the date.

draw several graphs javascript

I would use this code to display a given number of a graph, the number of such graph is variable each time will modifié.j 'I try to make a loop for the number of graph but I can not come me help
http://jsfiddle.net/amcharts/j9gUu/
my test
for (int i=1; i<4;i++)
{
var valueAxis[i] = new AmCharts.ValueAxis();
valueAxis[i].axisColor = "#FF6600";
valueAxis[i].axisThickness = 2;
valueAxis[i].gridAlpha = 0;
chart.addValueAxis(valueAxis[i]);
​
You can manipulate data from the server and assign them to the variables that I have hardcoded. Using this you can draw multiple line graphs.
chart = new AmCharts.AmSerialChart();
chart.categoryField = "date";// whatever your horizontal axis's value
chart.startDuration = 1;
// AXES
// category
var categoryAxis = chart.categoryAxis;
categoryAxis.labelRotation = 45;
categoryAxis.autoGridCount = true;
categoryAxis.startOnAxis = true;
// Value
var valueAxis = new AmCharts.ValueAxis();
valueAxis.gridAlpha = 0.07;
valueAxis.autoGridCount = true;
valueAxis.title = "Some Title on Vertical Axis";
chart.addValueAxis(valueAxis);
//following values hardcoded. you can assign any values you want.
var j=0;
var chartData = [[{"date":"2013 Mar","company1":0,"company2":0},{"date":"2013 Apr","company1":0,"company2":271},{"date":"2013 May","company1":0,"company2":271},{"date":"2013 Jun","company1":0,"company2":284},{"date":"2013 Jul","company1":509.9,"company2":1568}];
var no_of_companies = 2;//put your value
var color_array = your color array;//should be an array
chart.dataProvider = chartData;
for(j=0;j<no_of_companies;j++){
var graph = new AmCharts.AmGraph();
graph.valueField = "company"+(j+1);
graph.balloonText = "[[category]]: [[value]]";
graph.type = "line";
graph.lineColor = color_array[j];
graph.lineThickness = 2;
chart.addGraph(graph);
}
var chartCursor = new AmCharts.ChartCursor();
chartCursor.cursorAlpha = 0;
chartCursor.cursorPosition = "mouse";
chart.addChartCursor(chartCursor);
chart.write("chartdiv");
hope this would help you.

Categories

Resources