Reputation: 565
I'm working with multi-series data yielding dense scatter plots. Sometimes if two or more series produce a lot of points at more or less the same regions in the plot, it becomes difficult to interpret the plot due to google charts plotting all points of a series first, then the other and so on. This means all points of a series always end up behind or above all the points of other series. My ideal plot would have points randomly drawn independent from their series.
I made a simplified example to illustrate what I mean:
google.charts.load('current', {
packages: ['corechart']
}).then(function () {
var data = {
cols: [
{id: "x", label: "xValues", type: "number", role: "domain"},
{id: "y0", label: "y0Values", type: "number", role: "data"},
{id: "y1", label: "y1Values", type: "number", role: "data"}
],
rows: [
{c: [{v: 0.1}, {v: 0.5}, {}]},
{c: [{v: 0.1}, {}, {v: 0.5}]},
{c: [{v: 0.6}, {v: 0.1}, {}]},
{c: [{v: 0.6}, {}, {v: 0.1}]}
]
};
var dt = new google.visualization.DataTable(data);
var options = {seriesType: 'scatter'};
var chart = new google.visualization.ComboChart(document.getElementById('container'));
chart.draw(dt, options);
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="container"></div>
In this example we have 4 data points, 2 for each of the 2 series in the data. What I want to do is to plot the point (0.1, 0.5)
from series y0
, then plot (0.1, 0.5)
from y1
, then plot (0.6, 0.1)
from y1
and finally (0.6, 0.1)
from y0
. The result would be that only y1
is visible at (0.1, 0.5)
and only y0
is visible at (0.6, 0.1)
. Right now only y1
is visible at both locations. I think it is possible to manipulate the order of point plotting by series (i.e. plot ALL points of a series first, last, etc.) but I want to control the plotting order in a point-by-point basis.
Note that the order in which the rows appear in the data does not actually make a difference in how the points are plotted (the example has the points for each series alternated). This was the first thing I tried changing but it does not work. There are other things one can do to help the problem of dense scatters (using point alpha, changing to a hollow shape or implementing some interactivity on the chart to hide or show points) but I would still benefit from controlling the order of point plotting if possible.
Upvotes: 1
Views: 161
Reputation: 61222
when the chart is drawn, each series is drawn in order.
series 1 first, then series 2, etc...
changing the order of the rows will not affect this sequence.
SVG will display the last drawn element on top.
see this answer for more --> How to use z-index in svg elements?
in order to control which points are displayed on top,
you will need to modify the SVG on the chart's 'ready'
event.
we can take a couple of approaches, based on the referenced answer.
<use>
element takes nodes from within the SVG document, and duplicates them somewhere else.<use>
element to duplicate a point and add it after the other points.see following working snippet...
google.charts.load('current', {
packages: ['corechart']
}).then(function () {
var data = {
cols: [
{id: "x", label: "xValues", type: "number", role: "domain"},
{id: "y0", label: "y0Values", type: "number", role: "data"},
{id: "y1", label: "y1Values", type: "number", role: "data"}
],
rows: [
{c: [{v: 0.1}, null, {v: 0.5}]},
{c: [{v: 0.6}, null, {v: 0.1}]},
{c: [{v: 0.1}, {v: 0.5}, null]},
{c: [{v: 0.6}, {v: 0.1}, null]},
]
};
var dt = new google.visualization.DataTable(data);
var options = {seriesType: 'scatter'};
var chart = new google.visualization.ComboChart(document.getElementById('container'));
google.visualization.events.addListener(chart, 'ready', function () {
// points parent
var parent;
// svg namespace
var svgNS = chart.getContainer().getElementsByTagName('svg')[0].namespaceURI;
// find points
var circles = chart.getContainer().getElementsByTagName('circle');
Array.prototype.forEach.call(circles, function(circle, index) {
// skip legend circles
var rowIndex = (index - (dt.getNumberOfColumns() - 1));
if (rowIndex >= 0) {
// set element id
circle.id = 'circle' + rowIndex;
parent = circle.parentNode;
}
});
// add first point to end of order
var order = document.createElementNS(svgNS, 'use');
order.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#circle0');
parent.appendChild(order);
});
chart.draw(dt, options);
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="container"></div>
see following working snippet...
google.charts.load('current', {
packages: ['corechart']
}).then(function () {
var data = {
cols: [
{id: "x", label: "xValues", type: "number", role: "domain"},
{id: "y0", label: "y0Values", type: "number", role: "data"},
{id: "y1", label: "y1Values", type: "number", role: "data"}
],
rows: [
{c: [{v: 0.1}, null, {v: 0.5}]},
{c: [{v: 0.6}, null, {v: 0.1}]},
{c: [{v: 0.1}, {v: 0.5}, null]},
{c: [{v: 0.6}, {v: 0.1}, null]},
]
};
var dt = new google.visualization.DataTable(data);
var options = {seriesType: 'scatter'};
var chart = new google.visualization.ComboChart(document.getElementById('container'));
google.visualization.events.addListener(chart, 'ready', function () {
// find points
var circles = chart.getContainer().getElementsByTagName('circle');
Array.prototype.forEach.call(circles, function(circle, index) {
// skip legend circles
var rowIndex = (index - (dt.getNumberOfColumns() - 1));
// move first point to end
if (rowIndex === 0) {
circle.parentNode.appendChild(circle);
}
});
});
chart.draw(dt, options);
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="container"></div>
other options would be to change the pointSize
of each series, so at least some of the point is visible.
or simply not draw the point if it is going to cover another...
Upvotes: 1