Proper gradient for bars/area

TeeChart for Java (NetBeans, Eclipse, Android Studio, etc)
Post Reply
znakeeye
Newbie
Newbie
Posts: 44
Joined: Mon Jan 07, 2013 12:00 am

Proper gradient for bars/area

Post by znakeeye » Tue Mar 12, 2013 11:04 pm

http://stackoverflow.com/questions/1415 ... tchart-bar
Was TJ71016478 fixed?

Also, I believe this answer is incomplete.
http://stackoverflow.com/questions/1401 ... a-gradient
In your answer, you set the start color to green:

Code: Select all

band1.getGradient().setStartColor(Color.green);
What if your maximum value was 350 instead of 675? The color at that point should be yellow, but your series band will produce green in this case. It does not respect min/max:

Code: Select all

chart.getAxes().getLeft().setMinimum(0.0);
chart.getAxes().getLeft().setMaximum(675.0);
What I'm trying to do is to get a gradient for my bar and area charts that always shows the same color at a given y-position. This way, the color becomes an indicator of the value.
nice_bar_graph.png
Imagine if the color at the top was red. It should NOT get painted unless we reach the maximum value.
nice_bar_graph.png (3.15 KiB) Viewed 15698 times
nice_area_graph.png
Nice area :P
nice_area_graph.png (3.85 KiB) Viewed 15683 times
Ideally, I would want multiple gradient colors. Not just three!

Yeray
Site Admin
Site Admin
Posts: 9614
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Proper gradient for bars/area

Post by Yeray » Thu Mar 14, 2013 6:26 pm

Hello,
znakeeye wrote: http://stackoverflow.com/questions/1415 ... tchart-bar
Was TJ71016478 fixed?
I'm afraid not yet.
znakeeye wrote:Also, I believe this answer is incomplete.
http://stackoverflow.com/questions/1401 ... a-gradient
In your answer, you set the start color to green:

Code: Select all

    band1.getGradient().setStartColor(Color.green);
What if your maximum value was 350 instead of 675? The color at that point should be yellow, but your series band will produce green in this case. It does not respect min/max:

Code: Select all

    chart.getAxes().getLeft().setMinimum(0.0);
    chart.getAxes().getLeft().setMaximum(675.0);
What I'm trying to do is to get a gradient for my bar and area charts that always shows the same color at a given y-position. This way, the color becomes an indicator of the value.
I think I understood what you want to do. See the following code:

Code: Select all

		  tChart1.getAspect().setView3D(false);
		  tChart1.getLegend().setVisible(false);
		  
		  Axis bottom1 = new Axis();
		  Axis bottom2 = new Axis();
		  bottom1.setHorizontal(true);
		  bottom2.setHorizontal(true);
		  tChart1.getAxes().getCustom().add(bottom1);
		  tChart1.getAxes().getCustom().add(bottom2);
		  bottom1.setEndPosition(50);
		  bottom2.setStartPosition(50);
		  
		  Bar bar1 = new Bar(tChart1.getChart());
		  bar1.setCustomHorizAxis(bottom1);
		  bar1.getGradient().setVisible(true);
		  bar1.setGradientRelative(true);
		  bar1.getGradient().setStartColor(Color.green);
		  bar1.getGradient().setMiddleColor(Color.yellow);
		  bar1.getGradient().setUseMiddle(true);
		  bar1.getGradient().setEndColor(Color.red);
		  bar1.getMarks().setVisible(false);
		  
		  tChart1.getAspect().setView3D(false);
		  tChart1.getLegend().setVisible(false);

		  Area area1 = new Area(tChart1.getChart());
		  area1.setCustomHorizAxis(bottom2);
		  area1.setOrigin(0);
		  area1.setUseOrigin(true);
		  area1.getAreaLines().setVisible(false);
		  area1.getGradient().setVisible(true);
		  
		  for (int i=0; i<4; i++) {
			  bar1.add((i+1)*10);
			  area1.add((i+1)*20);
		  }
		  
		  tChart1.getWalls().getBack().getGradient().setVisible(true);
		  tChart1.getWalls().getBack().getGradient().setDirection(GradientDirection.VERTICAL);
		  tChart1.getWalls().getBack().getGradient().setStartColor(Color.green);
		  tChart1.getWalls().getBack().getGradient().setMiddleColor(Color.yellow);
		  tChart1.getWalls().getBack().getGradient().setUseMiddle(true);
		  tChart1.getWalls().getBack().getGradient().setEndColor(Color.red);
		  tChart1.getAxes().getLeft().setMinMax(0, 100);

		  bar1.setBarStyleResolver(new BarStyleResolver() {
			
			@Override
			public BarStyle getStyle(ISeries series, int valueIndex, BarStyle style) {
				if (series!=null) {
					Gradient seriesGradient = ((Bar)series).getGradient();
					Color tmpStartColor = null, tmpMidColor = null;
					
					if (valueIndex > -1) {
						double tmpValue = series.getYValues().getValue(valueIndex);
						if (tmpValue>50) {
							double percent = (tmpValue-50)*2;
							tmpStartColor = calcColorBlend(seriesGradient.getStartColor(), seriesGradient.getMiddleColor(), percent);
							tmpMidColor = calcColorBlend(seriesGradient.getStartColor(), seriesGradient.getMiddleColor(), percent/2);
						}
						else {
							double percent = tmpValue*2;
							tmpStartColor = calcColorBlend(seriesGradient.getMiddleColor(), seriesGradient.getEndColor(), percent);
							tmpMidColor = calcColorBlend(seriesGradient.getMiddleColor(), seriesGradient.getEndColor(), percent/2);
						}
						tChart1.getGraphics3D().getGradient().setStartColor(tmpStartColor);
						tChart1.getGraphics3D().getGradient().setMiddleColor(tmpMidColor);
					}
					
				}
				return style;
			}
		  });
device-2013-03-14-192458.png
device-2013-03-14-192458.png (13.25 KiB) Viewed 15653 times
Now, something similar should be done with the Area. Note it could be a little bit more tricky with the Area:
- The PointerStyleResolver is the event that is called each time a point is being drawn. So you will have to set the area pointer visible, and hide it in the event, returning PointerStyle.NOTHING.
- In the drawing process, the area is drawn prior to the pointer, so prior to the PointerStyleResolver. This involves:
* You'll have to play with the indexes to find what value you have to take to calculate what area.
* You'll probably have to set the series gradient colors instead of directly setting the canvas gradient colors.
znakeeye wrote:Ideally, I would want multiple gradient colors. Not just three!
This is a feature request already present in the wish list to be implemented in future releases (TJ71016471).
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

znakeeye
Newbie
Newbie
Posts: 44
Joined: Mon Jan 07, 2013 12:00 am

Re: Proper gradient for bars/area

Post by znakeeye » Fri Mar 15, 2013 5:07 pm

Hi. Thanks for the clarification!

Setting each color from a specific gradient array is desirable but not a requirement. Using three colors is good enough for my needs. But even if I use SeriesBand the result is not 100% correct.

Recall your sample here:
http://stackoverflow.com/questions/1401 ... a-gradient

Try setting the maximum value to 2000. I would expect all points to be red/orange, but instead the whole gradient will be defined - including green color. If you believe this is indeed correct behavior, then please answer this:

How should I solve this?
a) Should I recalculate the gradient depending on my maximum value? That is, in your sample I would use a gradient from red to yellow since green is way above the value of ~675. If you think this is the right way to go, then please make the 3-color-gradient algorithm public. (I don't know where it resides in the code.)
b) Should TChart handle this using some kind of property? E.g. "setGradientFromValueRange(true)" or something...

znakeeye
Newbie
Newbie
Posts: 44
Joined: Mon Jan 07, 2013 12:00 am

Re: Proper gradient for bars/area

Post by znakeeye » Fri Mar 15, 2013 5:45 pm

I believe there is a simple solution to get this right. This works:

Code: Select all

if (dir == GradientDirection.VERTICAL) { 
    g = new LinearGradient(0, /*rectangle.y*/0, 0, rectangle.getBottom(),
        colors, null,
        Shader.TileMode.CLAMP);
}
If you ask me, the above is the correct way to apply a gradient on any chart. Perhaps you should add another GradientDirection enum that does exactly this?

znakeeye
Newbie
Newbie
Posts: 44
Joined: Mon Jan 07, 2013 12:00 am

Re: Proper gradient for bars/area

Post by znakeeye » Fri Mar 15, 2013 6:20 pm

Hi again. I couldn't wait, so I implemented n-color gradient. Yeray, please e-mail me (the e-mail is linked to my account) if you want my source code changes.

Yeray
Site Admin
Site Admin
Posts: 9614
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Proper gradient for bars/area

Post by Yeray » Tue Mar 19, 2013 10:31 am

znakeeye wrote:But even if I use SeriesBand the result is not 100% correct.

Recall your sample here:
http://stackoverflow.com/questions/1401 ... a-gradient

Try setting the maximum value to 2000. I would expect all points to be red/orange, but instead the whole gradient will be defined - including green color. If you believe this is indeed correct behavior, then please answer this:

How should I solve this?
a) Should I recalculate the gradient depending on my maximum value? That is, in your sample I would use a gradient from red to yellow since green is way above the value of ~675. If you think this is the right way to go, then please make the 3-color-gradient algorithm public. (I don't know where it resides in the code.)
b) Should TChart handle this using some kind of property? E.g. "setGradientFromValueRange(true)" or something...
Right. Even the relative gradients in the Bar series are always relative to the maximum value of the series. Right now, there's no property or method to set gradients relative to other values; in your case, relative to the axis scale. This was what I was trying to achieve here.
And I've improved that code a bit. Take a look at it:

Code: Select all

	final Color mStartColor = Color.green;
	final Color mMiddleColor = Color.yellow;
	final Color mEndColor = Color.red;
	final int mStartValue = 100;
	
	private void initializeChart() {		  
		  tChart1.getAspect().setView3D(false);
		  tChart1.getLegend().setVisible(false);
		  
		  tChart1.getPanel().setMarginUnits(PanelMarginUnits.PIXELS);
		  tChart1.getPanel().setMarginBottom(25);
		  Axis bottom1 = new Axis();
		  Axis bottom2 = new Axis();
		  bottom1.setHorizontal(true);
		  bottom2.setHorizontal(true);
		  tChart1.getAxes().getCustom().add(bottom1);
		  tChart1.getAxes().getCustom().add(bottom2);
		  bottom1.setEndPosition(50);
		  bottom2.setStartPosition(50);
		  
		  bottom1.getGrid().setVisible(false);
		  bottom2.getGrid().setVisible(false);
		  tChart1.getAxes().getLeft().getGrid().setVisible(false);
		  
		  Bar bar1 = new Bar(tChart1.getChart());
		  bar1.setCustomHorizAxis(bottom1);
		  bar1.getGradient().setVisible(true);
		  bar1.setGradientRelative(true);
		  bar1.getGradient().setUseMiddle(true);
		  bar1.getGradient().setEndColor(mEndColor);
		  bar1.getMarks().setVisible(false);

		  Area area1 = new Area(tChart1.getChart());
		  area1.setCustomHorizAxis(bottom2);
		  area1.setOrigin(0);
		  area1.setUseOrigin(true);
		  area1.getAreaLines().setVisible(false);
		  area1.getLinePen().setVisible(false);
	
		  for (int i=0; i<4; i++) {
			  bar1.add((i+1)*20);
			  area1.add((i+1)*20);
		  }
		  
		  area1.getGradient().setVisible(true);
		  area1.getGradient().setDirection(GradientDirection.VERTICAL);
		  area1.getGradient().setUseMiddle(true);
		  area1.getGradient().setEndColor(mEndColor);
		  area1.getPointer().setVisible(true);
		  
		  double tmpStartValue = area1.getYValues().getValue(1);
		  setGradient(area1.getGradient(), tmpStartValue);

		  tChart1.getWalls().getBack().getGradient().setVisible(true);
		  tChart1.getWalls().getBack().getGradient().setDirection(GradientDirection.VERTICAL);
		  tChart1.getWalls().getBack().getGradient().setStartColor(mStartColor);
		  tChart1.getWalls().getBack().getGradient().setMiddleColor(mMiddleColor);
		  tChart1.getWalls().getBack().getGradient().setUseMiddle(true);
		  tChart1.getWalls().getBack().getGradient().setEndColor(mEndColor);
		  tChart1.getAxes().getLeft().setMinMax(0, mStartValue);
		  
		  bar1.getPen().setVisible(false);
		  bar1.setBarStyleResolver(new BarStyleResolver() {
			
			@Override
			public BarStyle getStyle(ISeries series, int valueIndex, BarStyle style) {
				if (series!=null) {					
					if (valueIndex > -1) {
						double tmpValue = series.getYValues().getValue(valueIndex);
						setGradient(tChart1.getGraphics3D().getGradient(), tmpValue);
					}
				}
				return style;
			}
		  });
		  
		  area1.setPointerStyleResolver(new PointerStyleResolver() {
			
			@Override
			public PointerStyle getStyle(ISeries series, int valueIndex, PointerStyle style) {
				if (series!=null) {
					Gradient seriesGradient = ((Area)series).getGradient();
					if (valueIndex > -1) {
						double tmpValue = series.getYValues().getValue((valueIndex+2) % series.getCount());						
						setGradient(seriesGradient, tmpValue);
					}
				}
				return PointerStyle.NOTHING;
			}
		});
	}
	
	private void setGradient(Gradient gradient, double value) {
		double mMiddleValue = mStartValue/2;
		if (value>mMiddleValue) {
			double percent = (value-mMiddleValue)*2;
			gradient.setStartColor(calcColorBlend(mStartColor, mMiddleColor, percent));
		}
		else {
			double percent = value*2;
			gradient.setStartColor(calcColorBlend(mMiddleColor, mEndColor, percent));
		}
		
		value /= 2;
		if (value>mMiddleValue) {
			double percent = (value-mMiddleValue)*2;
			gradient.setMiddleColor(calcColorBlend(mStartColor, mMiddleColor, percent));
		}
		else {
			double percent = value*2;
			gradient.setMiddleColor(calcColorBlend(mMiddleColor, mEndColor, percent));
		}
	}
	
	private Color calcColorBlend(Color end, Color start, double percentage) {        
        int tmpA = start.getAlpha() + Utils.round((end.getAlpha()-start.getAlpha()) * percentage / 100);
        int tmpR = start.getRed() + Utils.round((end.getRed()-start.getRed()) * percentage / 100);
        int tmpG = start.getGreen() + Utils.round((end.getGreen()-start.getGreen()) * percentage / 100);
        int tmpB = start.getBlue() + Utils.round((end.getBlue()-start.getBlue()) * percentage / 100);
        
        return Color.fromArgb(tmpA, tmpR, tmpG, tmpB);
    }
However, I'm afraid this is still not perfect:
device-2013-03-19-111156.png
device-2013-03-19-111156.png (10.4 KiB) Viewed 15619 times
When the bar/area value is below middleValue, 50 in the case above, it works fine because we just have to find the start and middle colors appropriate to the percentages, both between yellow and red.
However, when the bar/area value is above middleValue, the shape to draw isn't regular: it should be a shape from the value to 50, with a startColor according to the value percentage and a full yellow middleColor. And the second shape would be a rectangle going from yellow 50 to red 0.
znakeeye wrote:I believe there is a simple solution to get this right. This works:

Code: Select all

    if (dir == GradientDirection.VERTICAL) {
        g = new LinearGradient(0, /*rectangle.y*/0, 0, rectangle.getBottom(),
            colors, null,
            Shader.TileMode.CLAMP);
    }
If you ask me, the above is the correct way to apply a gradient on any chart. Perhaps you should add another GradientDirection enum that does exactly this?
In what code example do you find this change makes a better gradient?
znakeeye wrote:Hi again. I couldn't wait, so I implemented n-color gradient. Yeray, please e-mail me (the e-mail is linked to my account) if you want my source code changes.
We'll be glad to take a look at that implementation, of course. Please, send the changes and an example to "info at steema dot com" referencing this thread.
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Post Reply