求算法:图表控件,如何确定坐标轴的范围和刻度?

olivemfc 2010-06-30 04:54:16
举例说明一下,下面三幅图,大家注意一下它们的坐标刻度。我设置了三组不同的数值,它自动计算出了最合适的坐标范围和刻度:0 - 2 - 4...、0 - 0.2 - 0.4...、19 - 20 - 21...

求解其中的算法和思路







...全文
1849 18 打赏 收藏 转发到动态 举报
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
二二他爸 2012-05-26
  • 打赏
  • 举报
回复
顶啊,我也求这算法
鸭不梨儿 2010-09-06
  • 打赏
  • 举报
回复
up,同求算法。。。
clareshao 2010-08-20
  • 打赏
  • 举报
回复
不知楼主有没有解决这个问题,最近我也一直在做这个东西,感觉好麻烦,因为用户的数据可能乱七八糟,真的不知道怎么去像Excel那样“智能”地去定义那个坐标轴
olivemfc 2010-07-20
  • 打赏
  • 举报
回复
最近忙,忘记这个贴了。楼上两位的算法我抽空研究一下,先谢谢了。
thomasdai 2010-07-05
  • 打赏
  • 举报
回复
thomasdai 2010-07-05
  • 打赏
  • 举报
回复
效果如下图所示:
http://hi.csdn.net/attachment/201007/5/1652455_1278313874fmBf.png
thomasdai 2010-07-05
  • 打赏
  • 举报
回复
我正好做过这样的算法,把我的思路提供给你参考一下。
1,求要显示的原始数据最大最小值,Dmin, Dmax
2,求以10为底的对数(这个函数可以自己写,比较容易),结果为Lmin, Lmax
Lmin, Lmax可能为负值,例如0.001返回-3, 但是-10还是返回1,1返回0
3,要显示的刻度数字个数为DigitCount = Max(Lmin, Lmax) + 2, 包含小数点和负号
4,根据你使用的绘图API获取文字显示宽度,例如Delphi中Canvas.TextWidth('H')求取H的像素宽度
5,计算DigitCount个数字的像素宽度对应原始数据中数据范围,因为你要有足够的空间显示每一个刻度。
取刻度间隔为10^Lmin,放大2,5,10倍直到能容纳DigitCount个数字, 设最后的刻度间隔为Grad
6,最小刻度坐标为Floor(Dmin/Grad) * Grad
7,从最小刻度开始每隔Grad绘制一个坐标直到窗口结束。


部分源代码如下:

//取得刻度最大最小值
PtMin := ViewPort.ScreenToUcs(Point2D(0, ClientHeight));
PtMax := ViewPort.ScreenToUcs(Point2D(ClientWidth, 0));
if ptMin.X > ptMax.X then
begin
swap := ptMax.X;
ptMax.X := ptMin.X;
ptMin.X := swap;
end;
if ptMin.Y > ptMax.Y then
begin
swap := ptMax.Y;
ptMax.Y := ptMin.Y;
ptMin.Y := swap;
end;
//最大最小刻度分别有几位
LogHMax := IntLog10(PtMax.X);
LogHMin := IntLog10(PtMin.X);
LogVMax := IntLog10(PtMax.Y);
LogVMin := IntLog10(PtMin.Y);
LabH := Max(Abs(LogHMax), Abs(LogHMin)) + 2;
if (LogHMin < 0) or (LogHMax < 0) then
LabH := LabH + 1;
LabV := Max(Abs(LogVMax), Abs(LogVMin)) + 2;
if (LogVMin < 0) or (LogVMax < 0) then
LabV := LabV + 1;
//取得刻度文字宽度
TextHW := Canvas.TextWidth('H');
LabH := TextHW * LabH + 10;
LabV := TextHW * LabV + 10;

//求一个Wcs距离,使得转换为Screen之后的距离>=LabH, LabV
MinGrad := Viewport.DcsToVcs(Point2D(LabH, LabV));
MinGrad := MinGrad - Viewport.DcsToVcs(Point2D(0, 0));
MinGradH := Abs(MinGrad.X);
MinGradV := Abs(MinGrad.Y);
itemp := IntLog10(MinGradH);
LabH := itemp;
GradHPrec := IntPower10(itemp);
itemp := IntLog10(PtMax.Y - PtMin.Y);
GradVPrec := IntPower10(itemp);
while GradHPrec < MinGradH do
GradHPrec := GradHPrec * 10;
while GradVPrec < MinGradV do
GradVPrec := GradVPrec * 10;

//横向刻度,从上往下绘制
PtNow := PtMin;
PtNow.X := Floor(PtMin.X / GradHPrec) * GradHPrec;
PtScrLast := ViewPort.UCStoScreen(PtNow);
//向右侧
itemp := 0;
while PtNow.X < PtMax.X do
begin
PtScr := ptScrLast; //ViewPort.UCStoScreen(PtNow);
if (PtScr.X > 20) and (ptScr.X < ClientWidth) then
begin
if itemp mod 10 = 0 then
begin
Canvas.MoveTo(PtScr.X, 0);
Canvas.LineTo(PtScr.X, 20);
Canvas.TextOut(PtScr.X + 2, -2, FloatToDigitStr(PtNow.X, LabH));
end
else if itemp mod 5 = 0 then
begin
Canvas.MoveTo(PtScr.X, 10);
Canvas.LineTo(PtScr.X, 20);
end
else begin
Canvas.MoveTo(PtScr.X, 15);
Canvas.LineTo(PtScr.X, 20);
end;
end;
PtNow.X := PtNow.X + GradHPrec/10;
PtScrLast := ViewPort.UCStoScreen(PtNow);
if Abs(ptScr.X - ptScrLast.X) < LabH then
GradHPrec := GradHPrec * 10;
itemp := itemp + 1;
if itemp > 2000 then
Break;
end;
Leaveye 2010-07-04
  • 打赏
  • 举报
回复
忘记说。以及不清不楚。

1. 取整 Vstep 的操作。

上面说的意思是取 Vdelta 的 5%、10%、…… 来进行选择。选择时,将其取整,看是否符合 Vstepmin 和 Vstepmax 的限制。满足条件时,将取整结果作为 Vstep 来使用。

2. 取整 Vstep 后。

大略上,取整后的 Vstep 不能将 Vdelta 整除,这就需要调整 Vmin 和 Vmax 了,同样需要将其取整。不同的是,Vmin 要向下取整,Vmax 要向上取整。这样调整后的刻度线、边界值,就会合理得多。
Leaveye 2010-07-04
  • 打赏
  • 举报
回复
对了。刻度线的确也是个问题。

首先,由于 Dmax - Dmin = Ddelta 是确定的。这个数值反映了显示对象的像素尺寸。

根据这个像素尺寸,我们可以界定,最大的坐标线数 Nstepmax 是多少。
(可以根据限制的最大、最小坐标刻度间距来推算)

这样,我们就有了已经得到的 Vdelta ,最大刻度线数 Nstepmax 。可以得到坐标刻度步长的最小值 Vstepmin 。

同理,可以求得坐标刻度步长的最大值 Vstepmax 。

有了这两个,在根据数据值,进行分析,从中选择一个合理的数据来作为刻度数值步长 Vstep 使用。
这个分析就可以根据 Vdelta 的值来讨论了。
其中一种合理的做法是以 Vdelta 的 5% 的数值为基础,进行取整,取整规则可以是限制有效数字位数。
当然还有其他做法。不管如何是要选择一个 Vstep 。

有了 Vstep ,事实上就可以进行画刻度了。当然现在是均匀拆分。

但是还可以进一步,可能需要调整起点。比如刻度线是 5 的倍数,而图像区域的下界是 2 。

当然最后这个只是一个调整方案。是否使用?如何选择?因人而异,因地不同。
Leaveye 2010-07-04
  • 打赏
  • 举报
回复
我做过类似的实现。这个其实非常简单。

根源的数据还是求 Imin 和 Imax 。区别是,需要将其求差,得到 Idelta 。

根据图表的输出范围有个尺寸,简单标记为 Dmin 和 Dmax 。
所需要的是一个映射关系,该映射把 Dmin 和 Dmax 分别映射为 Vmin 和 Vmax 。并且满足:
1. Vmin < Imin <= Imax < Vmax
2. Imin - Vmin = Vmax - Imax = Vmargin
3. Vmargin / ( Vmax - Vmin ) = 一个常数C (我习惯为 1/8 或 1/10)

需求明确到这里,该做的事情就很清楚了。

Vmax - Vmin = Vdelta = Idelta / ( 1 - C*2 )
Vmargin = Vdelta * C = Idelta * C / ( 1 - C * 2 )
Vmin = Imin - Vmargin
Vmax = Imax + Vmargin

到这里就有了坐标上下界,其余的也就不用说了。
michael122 2010-07-01
  • 打赏
  • 举报
回复
这个你必须有明确的需求啊
如果数据是7,8位的,你想取几位?
或者各种长度的都有,你又想取几位?
这些都是人为规定的,如果你制定了明确的需求,实现上不难
olivemfc 2010-07-01
  • 打赏
  • 举报
回复
我没有表达清楚吗?第二个图取了一位小数,因为这样正好适合那几个数据。如果我给的数据都是7、8位小数,或者各种大小各种长度的都有,又如何取整呢?

所以,我觉得这其中必须有一个比较智能的算法。我上面贴的三幅图,都是从Excel里面截的,大家有条件的可以自己试验试验。或者任何一种图表控件,都可以做到这样。

这个问题乍一看挺简单的,真的细想下去,想到写算法写代码实现出来的程度,才会发现真的好难。
michael122 2010-07-01
  • 打赏
  • 举报
回复
那就取整啊,或者取几位小数(第2个图取1位小数)
olivemfc 2010-07-01
  • 打赏
  • 举报
回复
楼上说的是一种思路,也是比较容易想到的一种方式,但是往下细想的话,就会发现:

1、这个“margin”很难确定
2、用范围除以数据个数的话,得出的结果可能是 xx.xxx 这样拖泥带水的数值,但是坐标刻度应尽量取整数,比如第三张图,或者根据数据的实际情况取最整齐的数,比如第二张图

总的来说,要做到智能,还是有些困难的,也是我来这问的初衷
olivemfc 2010-07-01
  • 打赏
  • 举报
回复
谢谢你如此关注我的问题,你回复了很多内容让我很感动。
但我们的思路看来有不少差异。我的初衷是想问一个算法,而你看起来很喜欢抓住我每次发言中的某一个词进行讲解或引申,结果越拉越远了。
我是来出题的,所以就不要问我“具体需求”了。如果能从题目中提取出“具体需求”来,那么这个算法也就出来了,我要问的正是这个算法。算法的目的就是把一个题目变成可以拿去变成代码的“具体需求”。
--
回到原点,图表控件这种常见控件,我想它在坐标轴计算上也已经有了一个成熟的做法了,希望能有做过图表的前辈指点一下这个做法是什么。
--
最后再次谢谢michael122朋友。
michael122 2010-07-01
  • 打赏
  • 举报
回复
你要搞清楚,计算机不会思考,你所谓的智能其实就是设计很好的程序
连你自己都不知道你的程序要设计成什么样子,只是说很”智能“,谁能写的出来?
看上去简单的功能,背后的实现有多复杂你知道吗?
如果说0.2 0.33 0.444 0.5555 0.66666这样的输入,我问你输出是什么,你自己都说不清楚,你怎么写程序来设计这个功能?
就算是再智能的机器人,也是人为规定的,碰到什么情况怎么做而已,越智能说明这种需求越复杂。
excel怎么就不是人为规定了? 软件的所有行为都是做软件的人根据具体的需求做出来的
就冲你一个”智能“,就可以开发excel了吗?
olivemfc 2010-07-01
  • 打赏
  • 举报
回复
有一个词我一直在使用而楼上的朋友一直没有注意到,就是 智能

比方说,如果让你写一个图表控件,你如何去人为规定

这不是 if - else 这样简单的逻辑

我不是在做一个有“明确需求”的项目,不是 if(多少位){取多少位} 这样固定的逻辑关系

我要做的是一个通用的控件

你不妨打开Excel用一下它的图表统计,用各种数据试一下,我想你就会明白了
michael122 2010-06-30
  • 打赏
  • 举报
回复
先扫描一遍求出最大最小值 max,min 然后max和min分别加/减 一个margin,这个margin应该和它们的值有关,比方说取0.1max之类的,这样求出的max和min是范围
然后刻度可取 (max-min)/数据的个数

大概是这样了,具体怎么实现是比较主观的

33,008

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧