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 ?
In terms of documentation, our product guidance department hasn't gotten to writing any more detailed articles on how to configure marker templates. So the only current information is available in the API doc, and in some of the provided samples. I'm not sure there is a custom marker sample for the chart, but there is one for the Map CTP which uses the same API.
The general idea is that when the chart is rendering markers for a series, it will do it in two passes. It will call measure for each marker and each marker is allowed to express how much size it would like. It will then call render for each marker, passing in the underlying HTML5 Canvas context. It tells you how much size its decided you can have, and where you are to render in x/y coordinates. And you can render whatever you want at that position. If you want, you could even float DOM content over the chart at that position to represent your marker, but be warned, that DOM content will block mouse events from hitting the underlying chart if you are not careful, and DOM content will not be "clipped" by the plot area automatically.
We apologize for the lack of documentation, but certainly feel free to keep asking me questions, and we can document the answers here, for now ;-)
Nic,
For isLogarithmic, this will get the ellipses to display again:
var x1 = $("#container").igDataChart("scaleValue", "xAxis", 1);
var x2 = $("#container").igDataChart("scaleValue", "xAxis", 2);
var y1 = $("#container").igDataChart("scaleValue", "yAxis", 1);
var y2 = $("#container").igDataChart("scaleValue", "yAxis", 2);
Scaling 0 into the logarithmic axis is undefined so we need to pick a different two values in order to measure. But I don't think this will actually give you your correct behavior. As you are deciding on a static multiplier for all the ellipse widths or heights.
But if you want the "width" or "height" to be directly meaningful in terms of the values on the underlying axis, then you will basically need to scale the values in the context of the position of each ellipse. And ellipse of width 8 would have different apparent visual widths depending on where on the scale it is plotted, correct? And wouldn't it have a different radius on one side vs the other? Making it more of a blob than an ellipse?
Thanks Graham, this is just what I was looking for ! The behaviour when ellipse centers move outside the chart boundaries is quite OK
I'm now left with a few remaining questions:
The data I'm charting is scientfic and covers many orders of magnitude. I was using logarithmic x and y axes ( isLogarithmic: true ). When I use logarithmic axes with the ellipticalMarker template, the scaling goes awry. Can this be corrected ?
I have the overview pane displayed. When I zoom into the chart, the ellipses in the overview don't seem to behave correctly. Can this be corrected ?
Is there any documentation covering adding new marker types ? I'd like to be able to stop bugging you !
Here this may help. It determines the width of one unit by asking the axes to scale various values into the chart's pixel space, and then uses those are multipliers of the width and height. This should also make the shapes get larger as you zoom in. Please note thought, that as you start to make the markers this large you run into a few limitations in the current version of the chart. Marker visibility is determined based on whether the center is in view, at present, so the larger the marker, the odder it looks if it vanishes due to its center moving out of view. Having a different visibility mode here would need to be a feature request, unfortunately. Here is the updated logic:
$(function () { 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 minRx; // var maxRx; // var minRy; // var maxRy; //var minRadius = 5; //var maxRadius = 20; var xDist = 0; var yDist = 0; var ellipticalMarker = { passStarting: function (passInfo) { // maxRx = Number.MIN_VALUE; // minRx = Number.MAX_VALUE; // maxRy = Number.MIN_VALUE; // minRy = Number.MAX_VALUE; // for (var i = 0; i < data.length; i++) { // maxRx = Math.max(maxRx, data[i].value1); // minRx = Math.min(minRx, data[i].value1); // maxRy = Math.max(maxRy, data[i].value2); // minRy = Math.min(minRy, data[i].value2); // } var x1 = $("#container").igDataChart("scaleValue", "xAxis", 0); var x2 = $("#container").igDataChart("scaleValue", "xAxis", 1); var y1 = $("#container").igDataChart("scaleValue", "yAxis", 0); var y2 = $("#container").igDataChart("scaleValue", "yAxis", 1); xDist = Math.abs(x1 - x2); yDist = Math.abs(y1 - y2); }, measure: function (measureInfo) { var item = measureInfo.data.item(), // width = ((item.value1 - minRx) / (maxRx - minRx)) * (maxRadius - minRadius) + minRadius, // height = ((item.value2 - minRy) / (maxRy - minRy)) * (maxRadius - minRadius) + minRadius; width = item.value1 * xDist; height = item.value2 * yDist; measureInfo.width = width; measureInfo.height = height; }, render: function (renderInfo) { var ctx = renderInfo.context, x = renderInfo.xPosition, y = renderInfo.yPosition, item = renderInfo.data.item(), width = renderInfo.availableWidth, height = renderInfo.availableHeight; ctx.fillStyle = renderInfo.data.actualItemBrush().fill(); ctx.strokeStyle = renderInfo.data.outline().fill(); ctx.lineWidth = 1.0 / Math.max(width, height); ctx.save(); 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 }, { name: "yAxis", type: "numericY", minimumValue: 0, maximumValue: 8 }], 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" }); });
-Graham
Hi,
So you would like the value1 and value2 to directly map to the width and height? I was basically scaling them based on the range of values that appeared in the data source. But if you want them to directly map to the width and height range we can do that too, and I'm guessing you'd want the size to adjust as you zoom in and out too. I'll see if I can adjust the sample to do that.