I'd like to create a scatter or bubble chart with elliptical markers, with the ellipse center and axes determined by data values.
Is there any simple approach I can take, perhaps using a custom template ?
Thanks Graham - that works. Many thanks for your excellent support
Ah, passing posts - please ignore this
Yes, in the case of logarithmic axes the apparent width will be dependent on position, and yes an ellipse (x - x0)^2/X^2 + (y - y0)^2/Y^2 = 1 will not be elliptical when plotted on logarithmic axes, but use of an ellipse is conventional and good enough !
I imagine that I'd need to calculate the scale factor for each point in measure ? In the call:
var x1 = $("#chart").igDataChart("scaleValue", "xAxis", 0);
is the coordinate in data space or in pixel space ? If the fomer I could presumably write within measure
var x1 = $("#chart").igDataChart("scaleValue", "xAxis", item.x - 0.5);var x2 = $("#chart").igDataChart("scaleValue", "xAxis", item.x + 0.5);var y1 = $("#chart").igDataChart("scaleValue", "yAxis", item.y - 0.5);var y2 = $("#chart").igDataChart("scaleValue", "yAxis", item.y + 0.5);
xDist = Math.abs(x1 - x2);yDist = Math.abs(y1 - y2);
and proceed as before ?
Here's an example of how you might try to correct the issues I describe above:
var data = [{ x: 2, y: 5, value1: 1.3, value2: .8 }, { x: 3, y: 3, value1: 1.5, value2: .8 }, { x: 1, y: 2, value1: 2, value2: 1 }, { x: 3, y: 2, value1: 1, value2: 1 }, { x: 5, y: 1, value1: 1.0, value2: 1.4 }, { x: 4, y: 3, value1: .2, value2: 1.2 }]; var xDist = 0; var yDist = 0; var chartWidget = $("#container"); var ellipticalMarker = { passStarting: function (passInfo) { }, measure: function (measureInfo) { //can't measure in this case, measure doesn't give you x,y position yet. }, render: function (renderInfo) { var ctx = renderInfo.context, x = renderInfo.xPosition, y = renderInfo.yPosition, item = renderInfo.data.item(), width = item.value1, height = item.value2, ux, uy; //get the axis values that equate to where this marker is being rendered. ux = chartWidget.igDataChart("unscaleValue", "xAxis", x); uy = chartWidget.igDataChart("unscaleValue", "yAxis", y); //see how wide the marker is in terms of pixels based on the item values being used as radius. width = (chartWidget.igDataChart("scaleValue", "xAxis", ux + width / 2.0) - x) * 2.0; height = (chartWidget.igDataChart("scaleValue", "yAxis", uy + height / 2.0) - y) * 2.0; //since we didn't measure, must update with how much size we actually used. renderInfo.availableWidth = width; renderInfo.availableHeight = height; //the chart is telling use what colors to use here ctx.fillStyle = renderInfo.data.actualItemBrush().fill(); ctx.strokeStyle = renderInfo.data.outline().fill(); ctx.lineWidth = 1.0 / Math.max(width, height); ctx.save(); //html5 canvas does support non circular arcs, so we either need to use a transform or rasterize it ourselves. ctx.translate(x, y); ctx.scale(width / 2.0, height / 2.0); ctx.translate(-x, -y); ctx.beginPath(); ctx.arc(x, y, 1, 0, 2.0 * Math.PI, false); ctx.fill(); ctx.stroke(); ctx.restore(); } }; $("#container").igDataChart({ width: "500px", height: "500px", dataSource: data, axes: [{ name: "xAxis", type: "numericX", minimumValue: 0, maximumValue: 8, isLogarithmic: true }, { name: "yAxis", type: "numericY", minimumValue: 0, maximumValue: 8, isLogarithmic: true }], series: [{ name: "series1", title: "Test Series", type: "scatter", xAxis: "xAxis", yAxis: "yAxis", xMemberPath: "x", yMemberPath: "y", markerTemplate: ellipticalMarker, showTooltip: true, tooltipTemplate: "tooltipTemplate" }], horizontalZoomable: true, verticalZoomable: true, windowResponse: "immediate" });
Note, though, that it is still drawing ellipses, and an ellipse that had radii that are defined in terms of the underlying scale would actually have a different radius in each direction, as far as I can tell. So this is not necessarily 100% correct. This leaves the issue of the OPD. Its a bit of an open issue at the moment that markers render at the same size in the OPD as in the normal chart. And is something we will be looking at adjusting in the future. There are some ways to work around this though, as the passStarting method will be notified of the size of the area the markers are being rendered in. But compensating for this gets a bit complex. The bubble series actually has a targetted correction here were it will scale the size of the markers being rendered in the OPD, but I'm not sure if the code we have above will play nicely with the bubble series. As the bubble series only has the one radius mapping it expects. -Graham
In addition, there is actually a completely seperate render phase that is used solely to generate a hit buffer, to aid in tooltips and mouse events over the markers. You are notified when this is occuring via the isHitTestRender property. When that is true, you can render some more simplified bounding box if you want, but you MUST use the color passed in by chart, as it helps the chart identify when the mouse is over the series.