fix(selection): render gold brick chart as TDX brick blocks with strong-red flag
Match the ZXB1 sub-chart formula: each brick spans between yesterday's and today's brick value via a stacked range bar — rising bricks are hollow red (solid on strong_red), falling bricks solid green. The chart API now also returns brick_strong_red per point.
This commit is contained in:
@@ -76,6 +76,7 @@ export interface SelectionChartPoint {
|
||||
trend_white: number | null
|
||||
trend_yellow: number | null
|
||||
brick_chart: number | null
|
||||
brick_strong_red: boolean | null
|
||||
}
|
||||
|
||||
export interface SelectionChart {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { buildSelectionChartOption } from "./selection-chart-option"
|
||||
function point(index: number): SelectionChartPoint {
|
||||
return {
|
||||
brick_chart: null,
|
||||
brick_strong_red: null,
|
||||
close: index + 10.5,
|
||||
d: index + 20,
|
||||
high: index + 11,
|
||||
@@ -116,8 +117,9 @@ describe("selection chart option", () => {
|
||||
const option = buildSelectionChartOption(brickPoints)
|
||||
const grids = option.grid as Array<Record<string, unknown>>
|
||||
const series = option.series as Array<{
|
||||
data: Array<{ itemStyle: { color: string }; value: number }>
|
||||
data: Array<{ itemStyle: Record<string, unknown>; value: number } | null>
|
||||
name: string
|
||||
stack?: string
|
||||
xAxisIndex: number
|
||||
yAxisIndex: number
|
||||
}>
|
||||
@@ -141,17 +143,44 @@ describe("selection chart option", () => {
|
||||
expect(valueAxes).toHaveLength(4)
|
||||
|
||||
const brick = series.find((item) => item.name === "金砖")
|
||||
expect(brick).toMatchObject({ xAxisIndex: 2, yAxisIndex: 2 })
|
||||
expect(brick).toMatchObject({
|
||||
stack: "brick",
|
||||
xAxisIndex: 2,
|
||||
yAxisIndex: 2,
|
||||
})
|
||||
expect(brick?.data[0]).toBeNull()
|
||||
expect(brick?.data[1]).toMatchObject({
|
||||
itemStyle: { color: "#dc2626" },
|
||||
itemStyle: {
|
||||
borderColor: "#dc2626",
|
||||
color: "transparent",
|
||||
},
|
||||
value: 1,
|
||||
})
|
||||
expect(brick?.data[2]).toMatchObject({
|
||||
itemStyle: { color: "#16a34a" },
|
||||
value: 0,
|
||||
value: 1,
|
||||
})
|
||||
|
||||
const jLine = series.find((item) => item.name === "J")
|
||||
expect(jLine).toMatchObject({ xAxisIndex: 3, yAxisIndex: 3 })
|
||||
})
|
||||
|
||||
it("draws strong-red bricks as solid blocks", () => {
|
||||
const brickPoints = Array.from({ length: 3 }, (_, index) => ({
|
||||
...point(index),
|
||||
brick_chart: index,
|
||||
brick_strong_red: index === 2,
|
||||
}))
|
||||
const option = buildSelectionChartOption(brickPoints)
|
||||
const series = option.series as Array<{
|
||||
data: Array<{ itemStyle: Record<string, unknown>; value: number } | null>
|
||||
name: string
|
||||
}>
|
||||
|
||||
const brick = series.find((item) => item.name === "金砖")
|
||||
expect(brick?.data[2]).toMatchObject({
|
||||
itemStyle: { color: "#dc2626" },
|
||||
value: 1,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -143,7 +143,7 @@ export function buildSelectionChartOption(
|
||||
xAxisIndex: 1,
|
||||
yAxisIndex: 1,
|
||||
},
|
||||
...(showBrickChart ? [buildBrickSeries(points)] : []),
|
||||
...(showBrickChart ? buildBrickSeries(points) : []),
|
||||
{
|
||||
connectNulls: false,
|
||||
data: points.map((point) => point.j),
|
||||
@@ -192,26 +192,63 @@ export function buildSelectionChartOption(
|
||||
}
|
||||
}
|
||||
|
||||
function buildBrickSeries(points: SelectionChartPoint[]): BarSeriesOption {
|
||||
return {
|
||||
data: points.map((point, index) => {
|
||||
const previous = points[index - 1]
|
||||
const rising =
|
||||
previous?.brick_chart != null &&
|
||||
point.brick_chart != null &&
|
||||
point.brick_chart > previous.brick_chart
|
||||
return {
|
||||
itemStyle: {
|
||||
color: rising ? BRICK_UP_COLOR : BRICK_DOWN_COLOR,
|
||||
},
|
||||
value: point.brick_chart ?? 0,
|
||||
}
|
||||
}),
|
||||
name: "金砖",
|
||||
type: "bar",
|
||||
xAxisIndex: 2,
|
||||
yAxisIndex: 2,
|
||||
}
|
||||
/**
|
||||
* Render the TDX-style brick blocks: each brick spans between yesterday's and
|
||||
* today's brick value — rising bricks are hollow red (solid when strong red),
|
||||
* falling bricks are solid green, matching the STICKLINE sub-chart formula.
|
||||
*
|
||||
* The range is stacked from an invisible base series so each bar starts at
|
||||
* min(prev, cur) instead of zero.
|
||||
*/
|
||||
function buildBrickSeries(points: SelectionChartPoint[]): BarSeriesOption[] {
|
||||
const bricks = points.map((point, index) => {
|
||||
const previous = points[index - 1]
|
||||
if (previous?.brick_chart == null || point.brick_chart == null) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
base: Math.min(previous.brick_chart, point.brick_chart),
|
||||
height: Math.abs(point.brick_chart - previous.brick_chart),
|
||||
rising: point.brick_chart > previous.brick_chart,
|
||||
strongRed: point.brick_strong_red === true,
|
||||
}
|
||||
})
|
||||
return [
|
||||
{
|
||||
barWidth: "72%",
|
||||
data: bricks.map((brick) => (brick ? brick.base : null)),
|
||||
itemStyle: { color: "transparent" },
|
||||
silent: true,
|
||||
stack: "brick",
|
||||
tooltip: { show: false },
|
||||
type: "bar",
|
||||
xAxisIndex: 2,
|
||||
yAxisIndex: 2,
|
||||
},
|
||||
{
|
||||
barWidth: "72%",
|
||||
data: bricks.map((brick) => {
|
||||
if (!brick) return null
|
||||
return {
|
||||
itemStyle: brick.rising
|
||||
? brick.strongRed
|
||||
? { color: BRICK_UP_COLOR }
|
||||
: {
|
||||
borderColor: BRICK_UP_COLOR,
|
||||
borderWidth: 1,
|
||||
color: "transparent",
|
||||
}
|
||||
: { color: BRICK_DOWN_COLOR },
|
||||
value: brick.height,
|
||||
}
|
||||
}),
|
||||
name: "金砖",
|
||||
stack: "brick",
|
||||
type: "bar",
|
||||
xAxisIndex: 2,
|
||||
yAxisIndex: 2,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function toCandlestickValue(point: SelectionChartPoint) {
|
||||
|
||||
@@ -39,6 +39,7 @@ const chart: SelectionChartData = {
|
||||
points: [
|
||||
{
|
||||
brick_chart: null,
|
||||
brick_strong_red: null,
|
||||
close: 10.5,
|
||||
d: 48,
|
||||
high: 11,
|
||||
|
||||
Reference in New Issue
Block a user