I have to create a chart by our website visitor numbers (preferably similar to those of Analytics by Google).
I got a text file filled with the time of visits in milliseconds like this:
[1505385301633, 0][1505219341916, 0][1504999958757,
0][1504854145481, 0][1504601608015, 0][1504385667271,
0][1504380372409, 0][1504112805811, 0][1503866251230,
0][1503865512105, 0]...
This is what I got so far working with Google Charts:
https://jsfiddle.net/2djpec3s/
Controls: drag to zoom, right-click to reset
<head>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load('current', {'packages':['corechart'], 'language':'de'});
google.charts.setOnLoadCallback(drawChart);
// not in use yet
/* var date = new Date(1507130849370);
date = msToDate(date);
function msToDate(date) // milliseconds to year, month, day
{
var year = date.getFullYear();
var month = date.getMonth();
var date = date.getDate(); // day
return new Date(year, month, date);
} */
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Date', 'Visitors'],
[new Date(1507130849370), 1],
[new Date(1507149651188), 1],
[new Date(1507191655367), 1],
[new Date(1507212560124), 1],
[new Date(1507231742263), 1],
[new Date(1507305748865), 1],
[new Date(1507455848163), 1],
[new Date(1507612393800), 1],
[new Date(1507825666582), 1],
[new Date(1507881840738), 1],
[new Date(1507921803759), 1],
[new Date(1508001467740), 1]
]);
var options = {
title: 'Statistics',
explorer: {
actions: [
'dragToZoom',
'rightClickToReset'
],
maxZoomIn: 0.001
},
hAxis: {
gridlines: {
units: {
years: {format: ['y']},
months: {format: ['MMMM']},
days: {format: ['E, d. MMM']},
hours: {format: ["HH:mm 'Uhr'"]},
minutes: {format: ["HH:mm 'Uhr'"]}
}
},
minorGridlines: {
units: {
days: {format: ['d. MMM']},
hours: {format: ["H 'Uhr'"]},
minutes: {format: ['HH:mm']}
}
}
},
legend: { position: 'bottom' }
};
var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));
chart.draw(data, options);
}
</script>
</head>
<body>
<div id="curve_chart" style="width: 900px; height: 500px"></div>
</body>
The problem is I just can't figure out, if or how the data can be joined/stacked in an specific zoom level.
An alternative way I'd like to avoid is to create up to 4 charts for year, month, day and hour and let them visually be swappable by using a selection box.
One way you can do this is to have your zoom level be how many datapoints you want to show per pixel. The following examples uses the average in the sample range but you could take peak value or whatever works for your application.
This scales well to huge arrays as the number of data points you have is always equal to width and maximum complexity is width * resolution. If you always want to take all the datapoints just set resolution to a high number (or leave it out)
function getSampledData(data, zoomLevel, scrollPosition, width, resolution) {
const sampledData = [];
const skip = Math.max(1, zoomLevel / resolution);
// iterate over each pixel of chart width
for(let i = 0; i < width; i++) {
let total = 0;
// scrollPosition is the start pixel position of virtual scroll
let startPoint = (i * zoomLevel) + (scrollPosition * zoomLevel);
// zoom level is how many data points you want to show per pixel
for(let j = startPoint; j < startPoint + zoomLevel && j < data.length; j += skip) {
total += data[j];
}
sampledData.push(total / resolution);
}
return sampledData;
}
Related
I want to add percentage marks to number labels and found a option for it.
{hAxis: {format: 'percent'}}
But it also multiple the number by 100.
For instance, {hAxis: { format:'#,###%'} } displays the values "1,000%", "750%", and "50%" for values 10, 7.5, and 0.5.
https://developers.google.com/chart/interactive/docs/customizing_axes#number-formats
How can I just add % to the labels?
you could try adding custom axis labels, or ticks...
using object notation, you can provide both a...
value (v:) and a formatted value (f:)
then using google's NumberFormat class, build each tick manually...
var formatPercent = new google.visualization.NumberFormat({
suffix: '%'
});
...
ticks.push({
v: data.getValue(i, 0),
f: formatPercent.formatValue(data.getValue(i, 0))
});
...
hAxis: {
ticks: ticks
}
see following working snippet...
google.charts.load('current', {
callback: drawChart,
packages:['corechart']
});
function drawChart() {
var data = google.visualization.arrayToDataTable([
['X', 'Y'],
[1, 1],
[10, 2],
[20, 3],
[30, 4]
]);
var formatPercent = new google.visualization.NumberFormat({
pattern: '#,##0',
suffix: '%'
});
var ticks = [];
for (var i = 0; i < data.getNumberOfRows(); i++) {
ticks.push({
v: data.getValue(i, 0),
f: formatPercent.formatValue(data.getValue(i, 0))
});
}
var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
chart.draw(data, {
hAxis: {
ticks: ticks
}
});
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>
I'm trying to make an all positive bubble chart have quadrants by drawing the quadrants using the baseline property like so:
var dataT = google.visualization.arrayToDataTable(.....);
var options = {
hAxis: {title: 'h axis',baseline:100},
vAxis: {title: 'v axis',baseline:20},
...}
var chart = new google.visualization.BubbleChart(...);
chart.draw(dataT,options);
Except the graph will keep changing depending on the query so the baselines will not be the same for all the graphs. I would like to be able to get the max axis value and divide it by 2 to set the baselines right in the middle of each axis.
Example:
var options = {
hAxis: {title: 'h axis',baseline:max_h_axis/2},
vAxis: {title: 'v axis',baseline:max_v_axis/2},
...
Is there any way of knowing the max axis values of the graph before drawing the graph?
the getColumnRange method works for this...
Returns the minimal and maximal values of values in a specified column. The returned object has properties min and max. If the range has no values, min and max will contain null.
you can also use this information to produce your own axis tick marks.
see following working snippet...
google.charts.load('current', {
callback: function () {
var data = google.visualization.arrayToDataTable([
['X', 'Y'],
[8, 120],
[4, 155],
[11, 140],
[4, 205],
[3, 35],
[6, 78]
]);
var ticksX = [];
var ticksY = [];
var numberOfTicks = 10;
var rangeX = data.getColumnRange(0);
var rangeY = data.getColumnRange(1);
var stepX = Math.ceil((rangeX.max - rangeX.min) / numberOfTicks);
for (var i = rangeX.min - stepX; i <= rangeX.max + stepX; i = i + stepX) {
ticksX.push(i);
}
var stepY = Math.ceil((rangeY.max - rangeY.min) / numberOfTicks);
for (var i = rangeY.min - stepY; i <= rangeY.max + stepY; i = i + stepY) {
ticksY.push(i);
}
var baseX = Math.ceil((rangeX.max - rangeX.min) / 2) + rangeX.min;
var baseY = Math.ceil((rangeY.max - rangeY.min) / 2) + rangeY.min;
var options = {
hAxis: {
title: 'h axis',
baseline: baseX,
ticks: ticksX
},
vAxis: {
title: 'v axis',
baseline: baseY,
ticks: ticksY
},
legend: 'none',
height: 600,
width: 600
};
var chart = new google.visualization.ScatterChart(document.getElementById('chart_div'));
chart.draw(data, options);
},
packages: ['corechart']
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>
I am using Google Visualisation to create line charts for my application. I have following requirements in that :
Manipulating events on legends (like doubleClick, which I have
solved somehow)
Wrapping the legends in two rows avoiding pagination (Most imp and required)
I have gone through the following questions to get solution for my answers:
1) Issue with legend pagination (Google Interactive chart API)
Issue : I would avoid playing with font-size because the number of legends may increase over time
2)How the legends on Google charts can be wrapped
Issue: I do not want legends to be anywhere else than at the position:bottom. And maxLines solution does not work on position : bottom
3) Is there any way I can avoid pagination in legends of a google visualisation chart and show all the lines in two lines in a single page?
Issue: This is another link, which mentions my problem, but no useful answers found on it.
4) Google Documentation :
Heading : Chart Legend Text and Style chdl, chdlp, chdls [All charts]
https://developers.google.com/chart/image/docs/chart_params#axis-label-styles-chxs
Heading : Setting Chart Margines
https://developers.google.com/chart/image/docs/chart_params#chart-margins-chma-all----charts
Heading : Tooltips
https://developers.google.com/chart/interactive/docs/customizing_tooltip_content#tooltips-an-introduction
Comment : These are few google documentation links where few legend manipulating properties are mentioned, but still they does not solve my problem.
5)https://github.com/google/google-visualization-issues/issues/1286
Comment : This is the link where I can see that, google has not provided many properties to manipulate legends, and no much useful information to solve my issue
6) Google charts legend manipulation
Comment : This is the only link, where I got a hint about how to solve my issue i.e. writing own legends. But there is no more links provided for documentation, no jsFiddle or no ref links apart from one link which is not useful for me.
While gone through all these, I can see only solution to solve my problem is to write my own custom legends. But I have no idea about how to write a complete element adding to google API.
Please guide me to go through this, any suggestions/links/refs/hints are welcome.
Thank you.
Example: Build custom legend, which syncs with data and chart...
google.charts.load('44', {
callback: drawChart,
packages: ['controls', 'corechart']
});
function drawChart() {
// adapted from a previous example
var colorPallette = ['#273746','#707B7C','#dc7633','#f1c40f','#1e8449','#2874a6','#6c3483','#922b21'];
var data = new google.visualization.DataTable();
data.addColumn('date', 'X');
data.addColumn('number', 'Y1');
data.addColumn('number', 'Y2');
data.addRow([new Date(2016, 0, 1), 1, 123]);
data.addRow([new Date(2016, 1, 1), 6, 42]);
data.addRow([new Date(2016, 2, 1), 4, 49]);
data.addRow([new Date(2016, 3, 1), 23, 486]);
data.addRow([new Date(2016, 4, 1), 89, 476]);
data.addRow([new Date(2016, 5, 1), 46, 444]);
data.addRow([new Date(2016, 6, 1), 178, 442]);
data.addRow([new Date(2016, 7, 1), 12, 274]);
data.addRow([new Date(2016, 8, 1), 123, 934]);
data.addRow([new Date(2016, 9, 1), 144, 145]);
data.addRow([new Date(2016, 10, 1), 135, 946]);
data.addRow([new Date(2016, 11, 1), 178, 747]);
// use view to add various columns for example purposes
var view = new google.visualization.DataView(data);
view.setColumns([0, 1, 2,
{
calc: function (data, row) {
return data.getValue(row, 1) + data.getValue(row, 2);
},
type: 'number',
label: 'Y3'
},
{
calc: function (data, row) {
return data.getValue(row, 2) - data.getValue(row, 1);
},
type: 'number',
label: 'Y4'
},
{
calc: function (data, row) {
return data.getValue(row, 1) * 2;
},
type: 'number',
label: 'Y5'
},
{
calc: function (data, row) {
return data.getValue(row, 2) * 3;
},
type: 'number',
label: 'Y6'
},
{
calc: function (data, row) {
return data.getValue(row, 1) * 1.5;
},
type: 'number',
label: 'Y7'
},
{
calc: function (data, row) {
return data.getValue(row, 1) * 1.5;
},
type: 'number',
label: 'Y8'
}
]);
var control = new google.visualization.ControlWrapper({
controlType: 'DateRangeFilter',
containerId: 'control_div',
options: {
filterColumnIndex: 0
}
});
var chart = new google.visualization.ChartWrapper({
chartType: 'LineChart',
containerId: 'chart_div',
options: {
chartArea: {
width: '80%'
},
// add colors for legend mapping
colors: colorPallette,
hAxis: {
format: 'MMM',
slantedText: false,
maxAlternation: 1
},
legend: 'none',
width: 320
}
});
// add legend marker
function addLegendMarker(markerProps) {
var legendMarker = document.getElementById('template-legend-marker').innerHTML;
for (var handle in markerProps) {
if (markerProps.hasOwnProperty(handle)) {
legendMarker = legendMarker.replace('{{' + handle + '}}', markerProps[handle]);
}
}
document.getElementById('legend_div').insertAdjacentHTML('beforeEnd', legendMarker);
}
// chart ready event
google.visualization.events.addListener(chart, 'ready', function () {
var legend = document.getElementById('legend_div');
// colors from chart
var colorPallette = chart.getOption('colors');
// clear previous legend
legend.innerHTML = '';
// add legend marker for each Y axis column - skip X axis --> i = 1
for (var i = 1; i < chart.getDataTable().getNumberOfColumns(); i++) {
var markerProps = {};
markerProps.index = i;
markerProps.color = colorPallette[i - 1];
markerProps.label = chart.getDataTable().getColumnLabel(i);
addLegendMarker(markerProps);
}
// add click event to each legend marker
var markers = legend.getElementsByTagName('DIV');
Array.prototype.forEach.call(markers, function(marker) {
marker.addEventListener('click', function (e) {
var marker = e.target || e.srcElement;
if (marker.tagName.toUpperCase() !== 'DIV') {
marker = marker.parentNode;
}
var columnIndex = parseInt(marker.getAttribute('data-columnIndex'));
document.getElementById('message_div').innerHTML = 'legend marker clicked = ' + chart.getDataTable().getColumnLabel(columnIndex);
}, false);
});
});
var dash = new google.visualization.Dashboard(document.getElementById('dashboard'));
dash.bind([control], [chart]);
dash.draw(view);
}
#legend_div {
text-align: center;
width: 320px;
}
.legend-marker {
display: inline-block;
padding: 16px 4px 8px 4px;
}
.legend-marker-color {
display: inline-block;
height: 12px;
width: 12px;
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="dashboard">
<div id="chart_div"></div>
<div id="legend_div"></div>
<br/>
<div id="control_div"></div>
<br/>
<div id="message_div"></div>
</div>
<!-- template for building marker -->
<script id="template-legend-marker" type="text/html">
<div class="legend-marker" data-columnIndex="{{index}}">
<div class="legend-marker-color" style="background-color: {{color}}"></div>
<span>{{label}}</span>
</div>
</script>
if ($("#source svg text[text-anchor='end']").length > 0){
var n = $("#source svg text[text-anchor='end']").length;
$("#source svg text[text-anchor='end']")[n-5].innerHTML = "";
$("#source svg text[text-anchor='end']")[n-4].innerHTML = "Create your own legend";
$("#source svg text[text-anchor='end']")[n-3].innerHTML = "Create your own legend";
$("#source svg text[text-anchor='end']")[n-2].innerHTML = "Create your own legend";
$("#source svg text[text-anchor='end']")[n-1].innerHTML = "";
}
I have the following plot at jsfiddle that I am trying to put together using dygraph:
var data = [];
function getData() {
data = [];
var theDate = new Date(2012, 01, 01, 0, 0, 0, 0);
for (var x = 0; x < 1000; x++) {
data.push([new Date(theDate.getTime()), Math.random()]);
theDate.setDate(theDate.getDate() + 1);
}
g.updateOptions({
'file': data
});
}
var g = new Dygraph(document.getElementById("div_g"), data, {
drawPoints: true,
showRoller: false,
rollPeriod: 1,
valueRange: [0.0, 1.2],
labels: ['Time', 'Random'],
xlabel: 'Time',
ylabel: 'Random',
legend: 'always',
showRangeSelector: true
});
function change(el) {
g.setVisibility(el.id, el.checked);
}
setInterval(
getData, 1000)
I would like to edit the axes limits on the fly by simple plot interaction (i.e. no use of text boxes, just use the canvas). Does anyone know if there is a way to do this?
I have to display around 20 lines within a line chart with Google's Line Chart. It might happen, that these lines overlap. What is the best way to add noise to the data so all the lines are visible.
the values for cat1, cat2 and cat3 are the same but I want that the it is evident from the image, that they are really close - so my idea is that the lines should not overlap but be a bit apart. The user cannot assume, that all the values overlap since for some events, let's say D, the values might be missing.
Given this chart:
function drawVisualization() {
// Create and populate the data table.
var data = google.visualization.arrayToDataTable([
['x', '#1', '#2', '#3'],
['A', 1, 1, 1],
['B', 2, 2, 2],
['C', 3, 3, 3],
['D', 4, 4, 4],
['E', 5, 5, 5],
['F', 6, 6, 6],
['G', 7, 7, 7],
['H', 8, 8, 8],
['I', 9, 9, 9],
]);
// Create and draw the visualization.
new google.visualization.LineChart(document.getElementById('visualization')).
draw(data, {width: 500, height: 400,
vAxis: {maxValue: 10}}
);
}
One way is just to add a consistent +/- per series:
function drawVisualization() {
// Create and populate the data table.
var data = google.visualization.arrayToDataTable([
['x', '#1', '#2', '#3'],
['A', 1, 1, 1],
['B', 2, 2, 2],
['C', 3, 3, 3],
['D', 4, 4, 4],
['E', 5, 5, 5],
['F', 6, 6, 6],
['G', 7, 7, 7],
['H', 8, 8, 8],
['I', 9, 9, 9],
]);
for (var i = 1;i < data.getNumberOfColumns();i++) {
// Algorithm to add +/- 0.1 for each series
var dither = Math.round((i - 1)/2)/5;
if ( (i - 1) % 2 == 0 ) {
dither = dither * -1;
}
for (var j = 0;j < data.getNumberOfRows();j++){
// Add dither to series to display differently, but keep same data for tooltip
data.setCell(j, i, data.getValue(j, i) + dither, data.getValue(j, i) + '', undefined)
}
}
// Create and draw the visualization.
new google.visualization.LineChart(document.getElementById('visualization')).
draw(data, {width: 500, height: 400,
vAxis: {maxValue: 10}}
);
}
The issue with this method is that if the values of your axes or data change significantly, you won't be able to see the gap (because the resolution of the screen won't be big enough). To get around this issue, we would need to manually set the min/max values of the axes in order to be able to come up with an appropriate factor. For instance, from this answer we can take the following algorithm to determine min and max axes values that approximate what google will set for us automagically:
// Take the Max/Min of all data values in all graphs
var totalMax = 345;
var totalMin = -123;
// Figure out the largest number (positive or negative)
var biggestNumber = Math.max(Math.abs(totalMax),Math.abs(totalMin));
// Round to an exponent of 10 appropriate for the biggest number
var roundingExp = Math.floor(Math.log(biggestNumber) / Math.LN10);
var roundingDec = Math.pow(10,roundingExp);
// Round your max and min to the nearest exponent of 10
var newMax = Math.ceil(totalMax/roundingDec)*roundingDec;
var newMin = Math.floor(totalMin/roundingDec)*roundingDec;
// Determine the range of your values
var range = newMax - newMin;
// Define the number of gridlines (default 5)
var gridlines = 5;
// Determine an appropriate gap between gridlines
var interval = range / (gridlines - 1);
// Round that interval up to the exponent of 10
var newInterval = Math.ceil(interval/roundingDec)*roundingDec;
// Re-round your max and min to the new interval
var finalMax = Math.ceil(totalMax/newInterval)*newInterval;
var finalMin = Math.floor(totalMin/newInterval)*newInterval;
We can add this all together, and see that it will even work if all the values are increased in factor by 10 (which wouldn't have worked with the hard-coded version):
function drawVisualization() {
// Create and populate the data table.
var data = google.visualization.arrayToDataTable([
['x', '#1', '#2', '#3'],
['A', 10, 10, 10],
['B', 20, 20, 20],
['C', 30, 30, 30],
['D', 40, 40, 40],
['E', 50, 50, 50],
['F', 60, 60, 60],
['G', 70, 70, 70],
['H', 80, 80, 80],
['I', 90, 90, 90],
]);
// Get max and min values for the data table
var totalMin = data.getValue(0,1);
var totalMax = data.getValue(0,1);
for (var i = 1;i < data.getNumberOfColumns();i++) {
for (var j = 0;j < data.getNumberOfRows();j++){
if ( data.getValue(j, i) < totalMin ) {
totalMin = data.getValue(j, i);
}
if ( data.getValue(j, i) > totalMax ) {
totalMax = data.getValue(j, i);
}
}
}
// Calculate grid line axes and min/max settings
// Figure out the largest number (positive or negative)
var biggestNumber = Math.max(Math.abs(totalMax),Math.abs(totalMin));
// Round to an exponent of 10 appropriate for the biggest number
var roundingExp = Math.floor(Math.log(biggestNumber) / Math.LN10);
var roundingDec = Math.pow(10,roundingExp);
// Round your max and min to the nearest exponent of 10
var newMax = Math.ceil(totalMax/roundingDec)*roundingDec;
var newMin = Math.floor(totalMin/roundingDec)*roundingDec;
// Determine the range of your values
var range = newMax - newMin;
// Define the number of gridlines (default 5)
var gridlines = 5;
// Determine an appropriate gap between gridlines
var interval = range / (gridlines - 1);
// Round that interval up to the exponent of 10
var newInterval = Math.ceil(interval/roundingDec)*roundingDec;
// Re-round your max and min to the new interval
var finalMax = Math.ceil(totalMax/newInterval)*newInterval;
var finalMin = Math.floor(totalMin/newInterval)*newInterval;
// Calculate Dither
for (var i = 1;i < data.getNumberOfColumns();i++) {
// Algorithm to add +/- 0.1 for each series
var dither = Math.round((i - 1)/2)/(10/newInterval);
if ( (i - 1) % 2 == 0 ) {
dither = dither * -1;
}
for (var j = 0;j < data.getNumberOfRows();j++){
// Add dither to series to display differently, but keep same data for tooltip
data.setCell(j, i, data.getValue(j, i) + dither, data.getValue(j, i) + '', undefined)
}
}
// Create and draw the visualization.
new google.visualization.LineChart(document.getElementById('visualization')).
draw(data, {width: 500, height: 400,
vAxis: {minValue: finalMin, maxValue: finalMax}}
);
}