Skip to content

Commit c3aca60

Browse files
committed
0.3.8
1 parent 2293d19 commit c3aca60

2 files changed

Lines changed: 109 additions & 52 deletions

File tree

README.md

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -399,27 +399,21 @@ The following showcases various chart types supported by YiShape-Math. Click on
399399
var optimizer = Opts.lbfgs();
400400

401401
// 定义目标函数(Rosenbrock函数)/ Define objective function (Rosenbrock function)
402-
var objFun = new IObjectiveFunction() {
403-
@Override
404-
public double computeObjective(IVector x) {
405-
double x1 = x.get(0).doubleValue();
406-
double x2 = x.get(1).doubleValue();
407-
return (1 - x1) * (1 - x1) + 100 * (x2 - x1 * x1) * (x2 - x1 * x1);
408-
}
409-
};
402+
IObjectiveFunction objFun = (IVector x) -> {
403+
double x1 = x.get(0).doubleValue();
404+
double x2 = x.get(1).doubleValue();
405+
return (1 - x1) * (1 - x1) + 100 * (x2 - x1 * x1) * (x2 - x1 * x1);
406+
};
410407

411408
// 定义梯度函数 / Define gradient function
412-
var grdFun = new IGradientFunction() {
413-
@Override
414-
public IVector computeGradient(IVector x) {
415-
double x1 = x.get(0).doubleValue();
416-
double x2 = x.get(1).doubleValue();
417-
double[] grad = new double[2];
418-
grad[0] = -2 * (1 - x1) - 400 * x1 * (x2 - x1 * x1);
419-
grad[1] = 200 * (x2 - x1 * x1);
420-
return Linalg.vector(grad);
421-
}
422-
};
409+
IGradientFunction grdFun = (IVector x) -> {
410+
double x1 = x.get(0).doubleValue();
411+
double x2 = x.get(1).doubleValue();
412+
double[] grad = new double[2];
413+
grad[0] = -2 * (1 - x1) - 400 * x1 * (x2 - x1 * x1);
414+
grad[1] = 200 * (x2 - x1 * x1);
415+
return Linalg.vector(grad);
416+
};
423417

424418
// 执行优化 / Execute optimization
425419
var initX = Linalg.ones(2);

src/main/java/com/yishape/lab/math/plot/javafx/renderers/BarChartRenderer.java

Lines changed: 96 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import com.yishape.lab.math.plot.javafx.JavaFxThemeManager;
77
import javafx.scene.canvas.GraphicsContext;
88
import javafx.scene.paint.Color;
9-
import javafx.scene.text.Font;
109
import javafx.scene.text.TextAlignment;
1110

1211
import java.util.List;
@@ -38,18 +37,9 @@ public void render(GraphicsContext gc, List<SeriesData> data, ChartConfig config
3837

3938
// 判断是否为分组柱状图
4039
boolean isGrouped = data.size() > 1;
41-
42-
// 计算所有数据的最大值
43-
double maxValue = Double.MIN_VALUE;
44-
int maxBars = 0;
45-
for (SeriesData s : data) {
46-
if (s.y == null) continue;
47-
maxBars = Math.max(maxBars, s.y.length());
48-
for (int i = 0; i < s.y.length(); i++) {
49-
maxValue = Math.max(maxValue, s.y.get(i));
50-
}
51-
}
52-
double[] yRange = new double[]{0, maxValue * 1.1};
40+
41+
// 与 Seaborn/matplotlib 一致:Y 轴包含 0,负值自基线向下延伸
42+
double[] yRange = computeVerticalBarYRange(data);
5343

5444
// 清空画布
5545
gc.setFill(themeManager.getBackgroundColor());
@@ -75,6 +65,57 @@ public void render(GraphicsContext gc, List<SeriesData> data, ChartConfig config
7565
}
7666
}
7767

68+
/**
69+
* 垂直柱图 Y 轴范围:必含 0;全非负时从 0 起算,全非正时到 0 止,有正有负时在两侧留边距。
70+
*/
71+
private static double[] computeVerticalBarYRange(List<SeriesData> data) {
72+
double minV = Double.POSITIVE_INFINITY;
73+
double maxV = Double.NEGATIVE_INFINITY;
74+
for (SeriesData s : data) {
75+
if (s.y == null) continue;
76+
for (int i = 0; i < s.y.length(); i++) {
77+
double v = s.y.get(i);
78+
if (!Double.isFinite(v)) continue;
79+
minV = Math.min(minV, v);
80+
maxV = Math.max(maxV, v);
81+
}
82+
}
83+
if (minV == Double.POSITIVE_INFINITY) {
84+
return new double[] { 0, 1 };
85+
}
86+
double pad;
87+
if (minV >= 0) {
88+
double hi = maxV;
89+
double span = Math.max(hi, 1e-15);
90+
pad = span * 0.08;
91+
return new double[] { 0, hi + pad };
92+
}
93+
if (maxV <= 0) {
94+
double lo = minV;
95+
double span = Math.max(-lo, 1e-15);
96+
pad = span * 0.08;
97+
return new double[] { lo - pad, 0 };
98+
}
99+
double span = maxV - minV;
100+
pad = span * 0.08;
101+
return new double[] { minV - pad, maxV + pad };
102+
}
103+
104+
private static double yPixelForValue(double v, double[] yRange, ChartConfig config, double chartHeight) {
105+
double span = yRange[1] - yRange[0];
106+
if (span <= 1e-30) span = 1;
107+
double t = (v - yRange[0]) / span;
108+
return config.height - config.paddingBottom - t * chartHeight;
109+
}
110+
111+
/** 横向柱图:数值映射到 X 像素(与垂直柱共用 {@code computeVerticalBarYRange} 的区间逻辑) */
112+
private static double xPixelForBarhValue(double v, double[] valueRange, ChartConfig config, double plotW) {
113+
double span = valueRange[1] - valueRange[0];
114+
if (span <= 1e-30) span = 1;
115+
double t = (v - valueRange[0]) / span;
116+
return config.paddingLeft + t * plotW;
117+
}
118+
78119
/**
79120
* 绘制柱状图专用的坐标轴(只显示Y轴和分类X轴)
80121
*/
@@ -128,18 +169,25 @@ private void renderSimpleBars(GraphicsContext gc, SeriesData series, ChartConfig
128169
double barWidth = chartWidth / numBars * 0.8;
129170
double barSpacing = chartWidth / numBars * 0.2;
130171

172+
double y0 = yPixelForValue(0, yRange, config, chartHeight);
173+
131174
// 绘制柱状图
132175
for (int i = 0; i < numBars; i++) {
133176
double value = series.y.get(i);
134-
double barHeight = (value / yRange[1]) * chartHeight;
177+
double yVal = yPixelForValue(value, yRange, config, chartHeight);
178+
double top = Math.min(yVal, y0);
179+
double barHeight = Math.abs(yVal - y0);
180+
if (value != 0 && barHeight < 1) {
181+
barHeight = 1;
182+
}
135183
double x = config.paddingLeft + i * (barWidth + barSpacing) + barSpacing / 2;
136-
double y = config.height - config.paddingBottom - barHeight;
137-
184+
double y = top;
185+
138186
// 绘制柱子
139187
Color barColor = Color.web(palette[i % palette.length]);
140188
gc.setFill(barColor);
141189
gc.fillRect(x, y, barWidth, barHeight);
142-
190+
143191
// 绘制边框 - 使用主题文本颜色
144192
gc.setStroke(themeManager.getTextColor());
145193
gc.setLineWidth(1);
@@ -148,19 +196,21 @@ private void renderSimpleBars(GraphicsContext gc, SeriesData series, ChartConfig
148196
double hitPx = x + barWidth / 2;
149197
double hitPy = y + barHeight / 2;
150198
JavaFxChartUtils.registerHit(config, hitPx, hitPy, i, value, series.name, 0, i);
151-
199+
152200
// 绘制X轴标签
153201
if (series.labels != null && i < series.labels.size()) {
154202
gc.setFill(themeManager.getTextColor());
155203
gc.setFont(themeManager.getLabelFont());
156204
gc.setTextAlign(TextAlignment.CENTER);
157-
gc.fillText(series.labels.get(i), x + barWidth / 2,
205+
gc.fillText(series.labels.get(i), x + barWidth / 2,
158206
config.height - config.paddingBottom + 20);
159207
}
160-
161-
// 绘制数值标签 - 使用主题文本颜色
208+
209+
// 绘制数值标签:正值在柱顶上方,负值在柱底下方(与常见 bar 图一致)
162210
gc.setFill(themeManager.getTextColor());
163-
gc.fillText(String.format("%.1f", value), x + barWidth / 2, y - 5);
211+
gc.setTextAlign(TextAlignment.CENTER);
212+
double labelY = value >= 0 ? y - 5 : y + barHeight + 14;
213+
gc.fillText(String.format("%.1f", value), x + barWidth / 2, labelY);
164214
}
165215
}
166216

@@ -182,6 +232,8 @@ private void renderGroupedBars(GraphicsContext gc, List<SeriesData> data, ChartC
182232
double barWidth = groupWidth * 0.8 / numGroups;
183233
double groupSpacing = groupWidth * 0.2;
184234

235+
double y0 = yPixelForValue(0, yRange, config, chartHeight);
236+
185237
// 绘制每个series的柱子
186238
for (int g = 0; g < numGroups; g++) {
187239
SeriesData series = data.get(g);
@@ -199,12 +251,17 @@ private void renderGroupedBars(GraphicsContext gc, List<SeriesData> data, ChartC
199251
// 值为0时不绘制(表示该组在该X位置无数据)
200252
if (value == 0) continue;
201253

202-
double barHeight = (value / yRange[1]) * chartHeight;
254+
double yVal = yPixelForValue(value, yRange, config, chartHeight);
255+
double top = Math.min(yVal, y0);
256+
double barHeight = Math.abs(yVal - y0);
257+
if (barHeight < 1) {
258+
barHeight = 1;
259+
}
203260

204261
// X位置: 组起始 + 组内偏移
205262
double groupStartX = config.paddingLeft + i * groupWidth + groupSpacing / 2;
206263
double x = groupStartX + g * barWidth;
207-
double y = config.height - config.paddingBottom - barHeight;
264+
double y = top;
208265

209266
gc.fillRect(x, y, barWidth - 2, barHeight);
210267
gc.setStroke(themeManager.getTextColor());
@@ -373,9 +430,8 @@ private void renderHorizontalBars(GraphicsContext gc, List<SeriesData> data, Cha
373430
SeriesData series = data.get(0);
374431
if (series.y == null || series.labels == null) return;
375432
int n = Math.min(series.y.length(), series.labels.size());
376-
double maxV = 0;
377-
for (int i = 0; i < n; i++) maxV = Math.max(maxV, series.y.get(i));
378-
if (maxV <= 0) maxV = 1;
433+
List<SeriesData> one = List.of(series);
434+
double[] xRange = computeVerticalBarYRange(one);
379435

380436
gc.setFill(themeManager.getBackgroundColor());
381437
gc.fillRect(0, 0, config.width, config.height);
@@ -393,22 +449,29 @@ private void renderHorizontalBars(GraphicsContext gc, List<SeriesData> data, Cha
393449
double rowH = chartH / n * 0.72;
394450
double gap = chartH / n * 0.28;
395451
boolean uniformColor = Boolean.TRUE.equals(series.extraData.get("uniformBarColor"));
452+
double plotW = chartW * 0.92;
453+
double x0line = xPixelForBarhValue(0, xRange, config, plotW);
396454
for (int i = 0; i < n; i++) {
397455
double v = series.y.get(i);
398456
double y = config.paddingTop + i * (rowH + gap);
399-
double x0 = config.paddingLeft;
400-
double x1 = config.paddingLeft + (v / maxV) * chartW * 0.92;
457+
double xVal = xPixelForBarhValue(v, xRange, config, plotW);
458+
double left = Math.min(x0line, xVal);
459+
double w = Math.abs(xVal - x0line);
460+
if (v != 0 && w < 1) {
461+
w = 1;
462+
}
401463
gc.setFill(Color.web(uniformColor ? palette[0] : palette[i % palette.length]));
402-
gc.fillRect(x0, y, x1 - x0, rowH);
464+
gc.fillRect(left, y, w, rowH);
403465
gc.setStroke(themeManager.getTextColor());
404-
gc.strokeRect(x0, y, x1 - x0, rowH);
466+
gc.strokeRect(left, y, w, rowH);
405467
gc.setFill(themeManager.getTextColor());
406468
gc.setFont(themeManager.getLabelFont());
407469
gc.setTextAlign(TextAlignment.RIGHT);
408470
gc.fillText(series.labels.get(i), config.paddingLeft - 8, y + rowH / 2 + 4);
409471
gc.setTextAlign(TextAlignment.CENTER);
410-
gc.fillText(String.format("%.1f", v), x1 + 18, y + rowH / 2 + 4);
411-
JavaFxChartUtils.registerHit(config, (x0 + x1) / 2, y + rowH / 2, i, v, series.name, 0, i);
472+
double labelX = v >= 0 ? Math.max(x0line, xVal) + 18 : Math.min(x0line, xVal) - 18;
473+
gc.fillText(String.format("%.1f", v), labelX, y + rowH / 2 + 4);
474+
JavaFxChartUtils.registerHit(config, left + w / 2, y + rowH / 2, i, v, series.name, 0, i);
412475
}
413476
}
414477

0 commit comments

Comments
 (0)