R Plotly ScatterPolar chart - how to rotate labels individually? - r

I'm trying to make a simple scatterpolar chart in R Plotly that has 60+ categorical labels on the radial axis. Because there's so many labels, the default method of placing the labels results in them overlapping each other at the top and bottom of the chart. I've tried setting layout.polar.angularaxis.tickangle to 90, -90, etc, but that rotates ALL labels by that amount. Is there a way to either A) rotate each individual label by a specified value such that I can set some labels to -90 degrees, others to -45, others to 0, others to +45, etc... or B) leave the label rotations at 0 (as they are now), but dodge them a bit so they don't overlap at the top and bottom of the chart? I think B would be easier and preferred, but not sure if either A or B are possible.
Here's a minimum reproducible example:
labs1 <- c("Red ", "Green ", "Blue ", "Yellow ", "Orange ",
"Purple ", "Pink ", "Black ", "White ", "Gray ")
labs2 <- c("Bookshelf", "Television", "Refridgerator",
"Toolbox", "Xylophone" , "Chromosome")
labs <- sample(paste0(rep(labs1, each = 6), rep(labs2, times = 10)), 60, replace = F)
df <- data.frame(Label = labs,
Radius = sample(0:100, 60, replace = F))
plot_ly(df) %>%
add_trace(type = 'scatterpolar', mode = 'markers',
r = ~Radius,
theta = ~Label,
fill = 'toself')
And here's the result:

It's been a while since you asked, but I just ran across your question. I've got a workaround.
If you'd like to duplicate the data I've used, I used set.seed(35) before calling the creation of df.
This uses the library htmlwidgets in addition to plotly.
I've added margin to the layout to account for label rotation. This may need to be modified. (For example, if you had really long labels, the margin would need to be larger.)
In the onRender call, I have several things happening.
The function detector: checks for labels that have collided with each other
The function angler: extracts the tick angle the label is associated with
The function quad: identifies which quadrant of the circle it's working with, then rotates the text accordingly
lbls is a node list of every label in the plot.
The for loop rotates through lbls, sending every two labels to the function detector. If a collision is detected, the indicators are sent to the angler function (which calls the quad function).
It's not perfect, but it works.
library(plotly)
library(htmlwidgets)
plot_ly(df, type = 'scatterpolar', mode = 'markers',
r = ~Radius, theta = ~Label, fill = 'toself') %>%
layout(margin = list(t = 75, r = 75, b = 75, l = 75)) %>% # make room for rotated labels
onRender(
"function(el, x) {
function detector(r1, r2) {
var r1 = r1.getBoundingClientRect(); /* catch associated real estate */
var r2 = r2.getBoundingClientRect();
return !(r2.left > r1.right || /* check for collisions */
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
}
function angler(ind) { /* get tick angle to rotate label */
var par = lbls[ind].parentNode.parentNode.children[ind];
var parAt = par.getAttribute('transform');
/* send extracted angle to quad function*/
quad(/(?<=rotate\\()[^)]*/.exec(parAt)[0], ind); /* using regex, after 'rotate(' -- except ')' */
}
function quad(ang, ind) {
var ang = Math.abs(Number(ang)); /* make the angle a positive number */
var gimmeXform = lbls[ind].getAttribute('transform'); /* extract values to modify */
var xer = lbls[ind].getAttribute('x'); /* 3rd & 4th quad, ctrl rotation pt */
var yer = lbls[ind].getAttribute('y'); /* 3rd & 4th quad, ctrl rotation pt */
if(ang <= 90) {
tang = ang * -.3; /* rotate ang * -x, text-anchor: start */
tanch = 'start';
} else if (ang > 90 && ang <= 180) {
tang = (180 - ang) * 0.3; /* rotate ang * +x, text-anchor: end */
tanch = 'end';
} else if (ang > 180 && ang < 270) {
tang = (ang - 180) * -.3; /* rotate ang * -x, text-anchor: end */
tang = '' + tang + ',' + xer + ',' + yer; /* add rotation controls*/
tanch = 'end';
} else {
tang = (360 - ang) * 0.3; /* rotate ang * +x, text-anchor: start */
tang = '' + tang + ',' + xer + ',' + yer; /* add rotation controls*/
tanch = 'start';
} /* rotate text */
lbls[ind].setAttribute('transform', gimmeXform + 'rotate(' + tang + ')');
lbls[ind].setAttribute('text-anchor', tanch);
}
lbls = document.querySelectorAll('g.angularaxistick > text');
inds = [];
for(i = 0; i < lbls.length; i+=2) {
if(detector(lbls[i], lbls[i + 1])) { /* if collision detected */
angler(i);
angler(i + 1);
}
}
}"
)
Whether you've plotted it with a ton of real estate:
Or very little real estate, it alters the labels:
FYI: if all of the labels overlap, it won't work.

Related

Is it possible to change borderColor for area bands (e_band2) in echarts4r?

Goal:
In my plot, I would like to have different colors for the border and the area of an area band. The plot was created with echarts4r using the e_band2() function.
Problem:
In the documentation I read that area bands can be customized through the itemStyle argument of the function. This works fine for all the other options (borderWidth, borderType, shadowBlur, shadowColor) I tested, but not for borderColor. However, the borderColor option seems to work at least partially, because the symbol in the legend has the desired border color, but not the area band in the plot. Does anyone know if there is another way to change the border color or is this a bug?
Reprex:
library(echarts4r)
library(dplyr)
data(EuStockMarkets)
as.data.frame(EuStockMarkets) |>
dplyr::slice_head(n = 200) |>
dplyr::mutate(day = 1:dplyr::n()) |>
e_charts(day) |>
e_band2(DAX, SMI, itemStyle = list(
borderWidth = 1,
color = "green",
borderColor = "red"
)) |>
e_y_axis(scale = TRUE)
For some reason, it's hardcoded to ignore your style settings for the stroke color (in SVG, that's the lines) by echarts4r.
However, you can change it back.
I've taken the code from the echarts4r package for the JS function renderBand and modified it to use the settings that are defined in the plot call (which is what you wanted and expected).
Note that I've made this a string.
renderBands2 <- "
function renderBands2(params, api) {
if (params.context.rendered) return;
params.context.rendered = true;
/* set polygon vertices */
let points = [];
let i = 0;
while (typeof api.value(0,i) != 'undefined' && !isNaN(api.value(0,i))) {
points.push(api.coord([api.value(0,i), api.value(1,i)])); /* lo */
i++;
}
for (var k = i-1; k > -1 ; k--) {
points.push(api.coord([api.value(0,k), api.value(2,k)])); /* up */
}
return {
type: 'polygon',
shape: {
points: echarts.graphic.clipPointsByRect(points, {
x: params.coordSys.x, y: params.coordSys.y,
width: params.coordSys.width, height: params.coordSys.height
})}, style: api.style()
};
}"
Apply this to your plot with htmlwidgets::onRender. This calls for the plot to be re-rendered with this new function for the data series.
as.data.frame(EuStockMarkets) |>
dplyr::slice_head(n = 200) |>
dplyr::mutate(day = 1:dplyr::n()) |>
e_charts(day) |>
e_band2(DAX, SMI, itemStyle = list(
borderWidth = 1, color = "green", borderColor = "red")) |>
e_y_axis(scale = TRUE) %>%
htmlwidgets::onRender(paste0(
"function(el, data) {
", renderBands2,
"
chart = get_e_charts(el.id);
opts = chart.getOption();
opts.series[0].renderItem = renderBands2;
chart.setOption(opts, true); /* all better! */
}"))

Unwanted behavior when using insidetextorientation = 'radial' in plotly sunburst

When using insidetextorientation = 'radial', even in a very simple plotly sunburst chart, some of the labels are grossly displaced.
library(plotly)
my_labels <- c("A","B","C","AA","AB","AC","BA","BB","CA","CB","CC")
my_parents <- c("","","","A","A","A","B","B","C","C","C")
my_values <- c(15,23,7,3,5,7,6,17,1,2,4)
plot_ly(labels = my_labels,parents = my_parents,values = my_values,type = 'sunburst',
branchvalues = 'total',insidetextorientation = 'radial')
Notice the "B" and "BA" on the left are misplaced. Similar issues occur when zooming in on any of the parents and do not persist with insidetextorientation = 'auto'. Is there a way to fix this?
On the plotly community forum the same question was asked 2 years ago, but not answered.
This isn't a great answer. It works, but when you click out of a parent so that everything is showing again, the plot has to stop moving before the labels will change back to the radial angles.
I've tried a lot of different ways to improve it. I think that letting Plotly package authors know is important; short of changing the code, I don't know of a better way to make this happen. The main problem is that the SVG paths that change when you enter and leave a parent are deleted and re-calculated. Any event that's attached is deleted at that point. (I tried both Plotly events and straight JS events.)
So instead of an event, it's on an interval. Every 100 milliseconds it checks to see if the labels need to be fixed.
plot_ly(labels = my_labels, parents = my_parents,
values = my_values, type = 'sunburst',
branchvalues = 'total', insidetextorientation = 'horizontal') %>%
htmlwidgets::onRender("function() {
pc = ['B', 'A', 'C', 'BB', 'BA', 'AC', 'AB', 'AA', 'CC', 'CB', 'CA'];
rpl = [0, -64, 28, -68, 28, -32, -80, 68, 40, 16, 4];
rx = /.*rotate\\((.*)\\)/g;
function fixer(){
tx = document.querySelectorAll('g > text');
if(tx.length === 11) { /*not when clicking subgroups*/
for(i=0;i<tx.length;i++){
wh = tx[i].getAttribute('data-unformatted');
tr = tx[i].getAttribute('transform');
rot = /.*rotate\\((.*)\\)/g.exec(tr);
if(rot !== null){ /*if a text rotation is designated*/
if(rpl[pc.indexOf(wh)] !== Number(rot)) {
rot = rot[1];
if(Number(rot) !== rpl[i] && wh === pc[i]){ /*if angle does not match & label does*/
beg = /(.*)rotate/.exec(tr)[1]; /*capture translate string*/
xy = beg + 'rotate(' + rpl[i] + ')'; /*build new transform string*/
tx[i].setAttribute('transform', xy); /*replace transform string with new*/
}
}
}
if(rot === null && wh === pc[i]) { /*if no rotation is present and label matches*/
str = tr + 'rotate(' + rpl[i] + ')'; /* build new transform string */
tx[i].setAttribute('transform', str); /*replace transform string with new*/
}
}
}
}
setInterval(fixer, 100);}") # check regularly! (every 100 ms)
I basically started with onRender set to just spit out the transform attribute for every label, so that I could get the order in which the labels appear (see pc in the JS) and the angles that the labels are placed at (see rpl in the JS). This was done while insidetextorientation = 'radial'. (I actually looked at all the text options: tangential, horizontal, auto, and radial.)
From there I tried MANY things to trigger the JS function fixer. In the end the only thing that was consistent enough for me to even share my work was using setInterval.
Update; Firefox friendly!
I really did try to figure this out for my initial answer. Here is the code without static arrays for the labels or angles. Let me know if you run into issues.
plot_ly(labels = my_labels, parents = my_parents,
values = my_values, type = 'sunburst',
branchvalues = 'total', insidetextorientation = 'horizontal') %>%
htmlwidgets::onRender("function(el, x) {
/* calculates the angles for each slice */
function angler (arVal, deg){ /* array of values in order; degrees each value unit*/
results = []; /* for storing the results */
theta = 0; /* for cumulative angle */
for(k = 0; k < arVal.length; k++){
here = arVal[k] * deg; /* units times degrees */
mrot = theta + (.5 * here); /* initial text angle */
if(mrot >= 90 && mrot < 270) {
mrot = mrot - 180; /* don't put text upside down */
}
if(mrot >= 270 && mrot < 360){
mrot = mrot - 360; /* don't put text upside down */
}
results[k] = mrot;
theta = theta + here; /* for the next loop */
}
return(results);
}
par = x.data[0].parents; /* collect the necessary data*/
val = x.data[0].values;
lab = x.data[0].labels;
parI = par.reduce((a, e, i) => { /*which rows have parents?*/
if (e == '') { a.push(i); };
return a;
}, []); /*in array a, if element e at pos i equates to ''*/
parC = par.reduce((a, e, i) => { /*which rows do NOT have parents?*/
if (e != '') { a.push(i); };
return a;
}, []); /*in array a, if element e at pos i equates to ''*/
alp = lab.map((i, j) => {return [i, val[j], par[j]]}); /* combine data */
palv = alp.filter((e, i) => parI.some(j => i === j)); /*parent array*/
calv = alp.filter((e, i) => parC.some(j => i === j)); /*children array*/
pvalS = palv.sort(function(a, b) {
return((a[1] > b[1]) ? -1 : ((a[1] == b[1]) ? 0 : 1));
}) /*parents sorted by values*/
parS = pvalS.map(function(j) {return j[0];}); /*extract ordered parents*/
csort = []; /* for ordered kids array*/
for(i = 0; i < parS.length; i++){ /* sort children by parent and size*/
arr = calv.filter(j => parS[i].includes(j[2]));
arr1 = arr.map(function(w) {return w[1]}); /*get just values*/
arr2 = arr1.sort(function(a, b){ return b - a });
bld = [];
for(k = 0; k < arr2.length; k++) {
arr3 = arr.filter(j => arr2[k] == j[1]);
csort = csort.concat(arr3);
}
}
/* get the order--- in reverse*/
cvo = csort.map(function(j){return j[1]});
cvov = Object.values(cvo).reverse();
pvo = pvalS.map(function(j) {return j[1];}); /*extract ordered parents' values*/
pvov = Object.values(pvo).reverse();
ctots = cvov.reduce((a, b) => a + b, 0); /* 45 */
rotV = 360/ctots; /* 8 in this example */
kids = angler(cvov, rotV); /* collect kids angles */
parents = angler(pvov, rotV); /* collect parent angles */
kidS = csort.map(function(j) {return j[0];}); /* extract kid labels in order */
/*pc = ['B', 'A', 'C', 'BB', 'BA', 'AC', 'AB', 'AA', 'CC', 'CB', 'CA'];*/
pc = Object.values(parS).concat(Object.values(kidS));
/*rpl = [0, -64, 28, -68, 28, -32, -80, 68, 40, 16, 4];*/
rpl = parents.reverse().concat(kids.reverse());
rx = /.*rotate\\((.*)\\)/g;
function fixer(){
tx = document.querySelectorAll('g > text');
if(tx.length === 11) { /*not when clicking subgroups*/
for(i=0;i<tx.length;i++){
wh = tx[i].getAttribute('data-unformatted');
tr = tx[i].getAttribute('transform');
rot = /.*rotate\\((.*)\\)/g.exec(tr);
if(rot !== null){ /*if a text rotation is designated*/
if(rpl[pc.indexOf(wh)] !== Number(rot)) {
rot = rot[1];
if(Number(rot) !== rpl[i] && wh === pc[i]){ /*if angle does not match & label does*/
beg = /(.*)rotate/.exec(tr)[1]; /*capture translate string*/
xy = beg + 'rotate(' + rpl[i] + ')'; /*build new transform string*/
tx[i].setAttribute('transform', xy); /*replace transform string with new*/
}
}
}
if(rot === null && wh === pc[i]) { /*if no rotation is present and label matches*/
str = tr + 'rotate(' + rpl[i] + ')'; /* build new transform string */
tx[i].setAttribute('transform', str); /*replace transform string with new*/
}
}
}
}
setInterval(fixer, 100);}") # check regularly! (every 100 ms)

Highlight/find data points in a 3d plotly scatter from the browser

Using code from a previous post I was able to add a search bar that highlights specific points on a scatter plot in plotly using R, which worked well in 2 dimensions. However, I have now produced a 3 dimensional plot with the same data and the code for the search bar no longer works. Is it possible to have a search bar in a 3 dimensional plot, and if so is there a way to alter the following code to achieve that?
p <- plot_ly(carsDf, x = ~Comp.1 , y = ~Comp.2, text = rownames(carsDf),
mode = "markers", color = ~cluster_name, marker = list(size = 11), type = 'scatter', mode = 'markers')
p <- htmlwidgets::appendContent(p, htmltools::tags$input(id='inputText', value='Merc', ''), htmltools::tags$button(id='buttonSearch', 'Search'))
p <- htmlwidgets::appendContent(p, htmltools::tags$script(HTML(
'document.getElementById("buttonSearch").addEventListener("click", function()
{
var i = 0;
var j = 0;
var found = [];
var myDiv = document.getElementsByClassName("js-plotly-plot")[0]
var data = JSON.parse(document.querySelectorAll("script[type=\'application/json\']")[0].innerHTML);
for (i = 0 ;i < data.x.data.length; i += 1) {
for (j = 0; j < data.x.data[i].text.length; j += 1) {
if (data.x.data[i].text[j].indexOf(document.getElementById("inputText").value) !== -1) {
found.push({curveNumber: i, pointNumber: j});
}
}
}
Plotly.Fx.hover(myDiv, found);
}
);')))
htmlwidgets::saveWidget(p, paste('pca', ".html", sep=""))
p
Thanks

Core-Plot: y-axis labels outside initially, but inside plot subsequently

I'm using Core-Plot 0.9 and have a seemingly peculiar problem. I have an X-Y plot with labels on the y-axis. When the plot is initially displayed, the labels are correctly positioned on the tick marks (actually offset 1.0) to the left of the axis itself, which is correct.
Then, I select a new set of data to display using a UIPickerView to set the x-axis starting point and regenerate the plot. On this and all subsequent regenerations of the plot, the y-axis labels show up inside the plot (to the right of the axis) and not on the tick marks (actually shifted upwards a bit.
I have created what I think is enough room on the left hand side of the plot to contain the y-axis labels. In particular, I have code as follows:
graph.paddingLeft = 0.0;
graph.plotAreaFrame.paddingLeft = 25.0;
y.labelOffset = 1.0f;
y.labelAlignment = CPTAlignmentLeft;
Here is the correct representation of the y-axis labels on initial loading.
(source: msyapps.com)
Here is the incorrect representation of the y-axis labels after scrolling the picker view on the left and regenerating the plot.
(source: msyapps.com)
Are you changing the tickDirection property? That's what controls which side the labels are on.
I have a workable solution, but it violates the commonly recommended action for displaying the y-axis. The annotations in the code snippet below indicates where I think I deviated from conventions and where I think I have added a unique solution for fixing the y-axis in place on subsequent redrawing of plot.
#pragma mark - Choose returns to chart
- (IBAction)returnsToChart:(int)startPt andInteger:(int)endPt; {
contentArray1 = [[NSMutableArray alloc] initWithObjects:nil];
dataContent = [[NSMutableArray alloc] initWithObjects:nil];
int xPosition = 0;
for (int i=startPt; i<endPt+1; ++i) {
[dataContent addObject:[returnsAll objectAtIndex:i]];
id x = [NSNumber numberWithFloat:xPosition];
id y = [NSNumber numberWithFloat:[[returnsAll objectAtIndex:i] floatValue]];
if ([[returnsAll objectAtIndex:i] isEqualToString:#""]) {
[contentArray1 addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:x, #"x", #"", #"y", nil]];
}
else {
[contentArray1 addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:x, #"x", y, #"y", nil]]; }
xPosition = xPosition + 1;
}
dataForPlot = contentArray1;
quarterlyReturnView.frame = CGRectMake(0.0f, 8.0f, 320.0f, 240.0f);
graph = [[CPTXYGraph alloc] initWithFrame:CGRectZero];
quarterlyReturnView.hostedGraph = graph;
//paddingLeft leaves room for y-axis labels
//paddingBottom must be set at zero otherwise and the space that must be added in order for the bottom-most label text to show will be handled by extending the length of the y-axis downward by a little bit
graph.paddingLeft = 8.0;
graph.paddingTop = 5.0;
graph.paddingRight = 5.0;
graph.paddingBottom = 0.0;
//convention says that paddingLeft should be some number greater than zero
//convention says that paddingBottom should be some number greater than zero, but if greater than zero, then each subsequent redrawing of plot results in a label shift upwards by the non-zero amount
graph.plotAreaFrame.paddingLeft = 0.0;
graph.plotAreaFrame.paddingTop = 5.0;
graph.plotAreaFrame.paddingRight = 5.0;
graph.plotAreaFrame.paddingBottom = 0.0;
// Setup plot space
CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)graph.defaultPlotSpace;
//this is where the magic happens...
//I tweaked the percentage adjustments for the location as a function of the number of data points along the x-axis that had to be plotted
//I settled on 8% plus and minus to provide room for the y-axis labels
//this percentage is expressed as -0.08*(datacontent.count-1.0) and 1.08*(datacontent.count-1.0)
//in other words, I put 8% on the left-hand side of the x-axis and made the whole x-axis 108% of the number of data points
plotSpace.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromFloat(-0.08 * (dataContent.count-1.0)) length:CPTDecimalFromFloat(1.08 * (dataContent.count-1.0))];
//Text styles
CPTMutableTextStyle *axisTitleTextStyle = [CPTTextStyle textStyle];
axisTitleTextStyle.fontName = #"Helvetica";
axisTitleTextStyle.fontSize = 9.0;
//Grid styles
CPTMutableLineStyle *majorGridLineStyle = [CPTLineStyle lineStyle];
majorGridLineStyle.lineWidth = 0.5f;
//Axis styles
CPTMutableLineStyle *axisLineStyle = [CPTLineStyle lineStyle];
axisLineStyle.lineColor = [CPTColor blueColor];
axisLineStyle.lineWidth = 1.0f;
float maxNum = -100.0;
float minNum = 100.0;
float testMaxNum = 0.0;
float testMinNum = 0.0;
for (int i=0; i<[dataContent count]; ++i) {
if ([[dataContent objectAtIndex:i] isEqualToString:#""]) { }
else {
testMaxNum = [[dataContent objectAtIndex:i] floatValue];
if (maxNum < testMaxNum) { maxNum = testMaxNum; }
testMinNum = [[dataContent objectAtIndex:i] floatValue];
if (minNum > testMinNum) { minNum = testMinNum; } }
}
int maxInt = (maxNum + 1.0) / 1.0;
float yMax = 1.0 * maxInt;
int minInt = (minNum - 1.0) / 1.0;
float yMin = 1.0 * minInt;
//here is where the y-axis is stretched a bit to allow the labels to show unclipped
plotSpace.yRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromFloat(yMin-0.2) length:CPTDecimalFromFloat(yMax-yMin+0.2)];
// Axes (x is horizontal; y is vertical)
CPTXYAxisSet *axisSet = (CPTXYAxisSet *)graph.axisSet;
CPTXYAxis *x = axisSet.xAxis;
x.axisLineStyle = axisLineStyle;
x.labelOffset = 1.0f;
x.labelTextStyle = nil;
x.majorTickLength = 5.0f;
x.majorIntervalLength = CPTDecimalFromString(#"16");
x.orthogonalCoordinateDecimal = CPTDecimalFromString(#"0");
x.minorTicksPerInterval = 0;
x.minorTickLength = 0.0f;
x.tickDirection =CPTSignNone;
x.visibleRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromFloat(0.0) length:CPTDecimalFromFloat(dataContent.count-1.0)];
CPTXYAxis *y = axisSet.yAxis;
y.axisLineStyle = axisLineStyle;
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setMaximumFractionDigits:0];
y.labelFormatter = formatter;
y.labelOffset = 2.0f;
y.labelAlignment = CPTAlignmentLeft;
y.labelTextStyle = axisTitleTextStyle;
y.tickDirection = CPTSignNegative;
y.majorTickLength = 3.0f;
y.majorIntervalLength = CPTDecimalFromString(#"2");
y.majorGridLineStyle = majorGridLineStyle;
//y.gridLinesRange is needed because the x-axis has been stretched 8% as described above
y.gridLinesRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromInt(0) length:CPTDecimalFromInt(dataContent.count-1)];
y.minorTicksPerInterval = 1;
y.minorTickLength = 2.0f;
y.orthogonalCoordinateDecimal = CPTDecimalFromString(#"0");
// Create plot area
CPTScatterPlot *boundLinePlot = [[CPTScatterPlot alloc] init];
CPTMutableLineStyle *lineStyle = [CPTMutableLineStyle lineStyle];
lineStyle.miterLimit = 1.0f;
lineStyle.lineWidth = 2.0f;
lineStyle.lineColor = [CPTColor blueColor];
boundLinePlot.dataLineStyle = lineStyle;
boundLinePlot.dataSource = self;
[graph addPlot:boundLinePlot];
[boundLinePlot reloadData];
}

ZedGraph labels

In ZedGraph, how do I show text labels for each point and in the XAxis all together?
If I do
myPane.XAxis.Type = AxisType.Text;
myPane.XAxis.Scale.TextLabels = array_of_string;
I get labels on the XAxis like this
And if I do
for (int i = 0; i < myCurve.Points.Count; i++)
{
PointPair pt = myCurve.Points[i];
// Create a text label from the Y data value
TextObj text = new TextObj(
pt.Y.ToString("f0"), pt.X, pt.Y + 0.1,
CoordType.AxisXYScale, AlignH.Left, AlignV.Center);
text.ZOrder = ZOrder.A_InFront;
text.FontSpec.Angle = 0;
myPane.GraphObjList.Add(text);
}
I get labels on the curve, like this
But if I do both at the same time, labels on the curve disappear.
Is there a way to combine both kind of labels?
I've changed my answer after you clarified the question.
You just have to remember to position the labels correctly:
<%
System.Collections.Generic.List<ZedGraphWebPointPair> points = new System.Collections.Generic.List<ZedGraphWebPointPair>();
for (int i = 0; i < 15; i++)
{
// Let's have some fun with maths
points.Add(new ZedGraphWebPointPair
{
X = i,
Y = Math.Pow(i - 10, 2) * -1 + 120
});
}
System.Collections.Generic.List<string> XAxisLabels = new System.Collections.Generic.List<string>();
TestGraph.CurveList.Add(new ZedGraphWebLineItem { Color = System.Drawing.Color.Red });
TestGraph.XAxis.Scale.FontSpec.Size = 9;
int j = 1;
foreach (ZedGraphWebPointPair point in points)
{
// Add the points we calculated
TestGraph.CurveList[0].Points.Add(point);
// Add the labels for the points
TestGraph.GraphObjList.Add(new ZedGraphWebTextObj
{
Location =
{
CoordinateFrame = ZedGraph.CoordType.XChartFractionYScale,
// Make sure we position them according to the CoordinateFrame
X = Convert.ToSingle(j) / points.Count - 0.05f,
Y = Convert.ToSingle(point.Y) + 3f,
AlignV = ZedGraph.AlignV.Top
},
Text = point.Y.ToString(),
FontSpec = { Angle = 90, Size = 9, Border = { IsVisible = false } }
});
// Add the labels for the XAxis
XAxisLabels.Add(String.Format("P{0}", j));
j++;
}
TestGraph.RenderGraph += delegate(ZedGraphWeb zgw, System.Drawing.Graphics g, ZedGraph.MasterPane mp)
{
ZedGraph.GraphPane gp = mp[0];
gp.XAxis.Type = ZedGraph.AxisType.Text;
gp.XAxis.Scale.TextLabels = XAxisLabels.ToArray();
};
%>
That code will produce this graph:
If the axis type is text, the code below is easier to get x-coordinates of the points ;)
for (int tPoint = 0; tPoint < curve.Points.Count; tPoint++)
{
TextObj text = new TextObj(curve.Points[tPoint].Y.ToString(), curve.Points[tPoint].X, curve.Points[tPoint].Y + 10);
}

Resources