Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,9 @@ private void drawK(Canvas canvas) {
if (mChildDraw != null) {
mChildDraw.drawTranslated(lastPoint, currentPoint, lastX, currentPointX, canvas, this, i);
}

// Draw buy/sell marks for this candlestick if they exist (O(1) lookup)
drawBuySellMarksForCandlestick((KLineEntity) currentPoint, i, currentPointX, canvas);
}

//还原 平移缩放
Expand Down Expand Up @@ -1062,6 +1065,99 @@ private void drawOrderLines(Canvas canvas) {
}
}

private void drawBuySellMarksForCandlestick(KLineEntity candlestick, int index, float currentPointX, Canvas canvas) {
// Get parent container view to access buy/sell marks
if (getParent() instanceof HTKLineContainerView) {
HTKLineContainerView containerView = (HTKLineContainerView) getParent();

// Use the preserved timestamp to avoid precision loss
long candleTime = candlestick.timestamp;

// Check for both marks to handle collision detection
java.util.Map<String, Object> buyMark = containerView.getBuyMarkForTime(candleTime);
java.util.Map<String, Object> sellMark = containerView.getSellMarkForTime(candleTime);

// Check if both marks exist to handle collision avoidance
boolean hasBothMarks = buyMark != null && sellMark != null;

// Draw buy mark
if (buyMark != null) {
drawBuySellMark(buyMark, candlestick, index, currentPointX, "buy", canvas, hasBothMarks);
}

// Draw sell mark
if (sellMark != null) {
drawBuySellMark(sellMark, candlestick, index, currentPointX, "sell", canvas, hasBothMarks);
}
}
}

private void drawBuySellMark(java.util.Map<String, Object> markData, KLineEntity candlestick, int index, float currentPointX, String type, Canvas canvas, boolean hasBothMarks) {
// Use the same X coordinate as candlesticks (same as mMainDraw.drawTranslated)
float candleX = currentPointX;

// Use the same Y coordinate calculation as MainDraw.drawCandle
float candleHigh = candlestick.getHighPrice();
float highY = yFromValue(candleHigh); // Same method used by MainDraw.drawCandle

// Circle properties - diameter should match candlestick width
float circleRadius = mPointWidth * 0.4f; // Use 80% of candlestick width for diameter

// Position both marks above the candlestick, with collision avoidance
float markCenterY;
if ("buy".equals(type)) {
// Buy mark directly above the candlestick
markCenterY = highY - circleRadius - dp2px(2);
} else { // sell
if (hasBothMarks) {
// If both marks exist, position sell mark one diameter higher
markCenterY = highY - circleRadius - dp2px(2) - (circleRadius * 2) - dp2px(2);
} else {
// If only sell mark exists, position it directly above
markCenterY = highY - circleRadius - dp2px(2);
}
}

android.util.Log.d("BuySellDebug", "Drawing " + type + " mark - visible at (" + candleX + ", " + markCenterY + ") radius: " + circleRadius);

// Determine colors based on type, using same colors as candlesticks
int circleColor;
if ("buy".equals(type)) {
circleColor = configManager.increaseColor; // Use same color as increasing candlesticks
} else { // sell
circleColor = configManager.decreaseColor; // Use same color as decreasing candlesticks
}

// Create paint for circle
Paint circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint.setColor(circleColor);
circlePaint.setStyle(Paint.Style.FILL);

// Draw circle
canvas.drawCircle(candleX, markCenterY, circleRadius, circlePaint);

// Draw border
Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
borderPaint.setColor(circleColor);
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(2.0f);
canvas.drawCircle(candleX, markCenterY, circleRadius, borderPaint);

// Draw text inside circle
String markText = "buy".equals(type) ? "B" : "S";
Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(android.graphics.Color.WHITE);
textPaint.setTextSize(circleRadius * 1.2f); // Text size proportional to circle
textPaint.setTypeface(configManager.font);
textPaint.setTextAlign(Paint.Align.CENTER);

// Calculate text position (center of circle)
Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
float textY = markCenterY - (fontMetrics.ascent + fontMetrics.descent) / 2;

canvas.drawText(markText, candleX, textY, textPaint);
}

public int dp2px(float dp) {
final float scale = getContext().getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ public KLineEntity packModel(Map<String, Object> keyValue) {
idValue = keyValue.get("time");
}
entity.id = idValue != null ? ((Number)idValue).intValue() : 0;
// Store original timestamp as long to preserve precision for buy/sell marks
entity.timestamp = idValue != null ? ((Number)idValue).longValue() : 0;

// Handle dateString with fallback
Object dateValue = keyValue.get("dateString");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public float getMA10Volume() {
public List<Map<String, Object>> selectedItemList = new ArrayList<>();

public float id;
public long timestamp; // Store original timestamp as long to avoid precision loss
public String Date;
public float Open;
public float High;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void run() {

@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of(
Map<String, Integer> commands = MapBuilder.of(
"updateLastCandlestick", 1,
"addCandlesticksAtTheEnd", 2,
"addCandlesticksAtTheStart", 3,
Expand All @@ -95,6 +95,14 @@ public Map<String, Integer> getCommandsMap() {
"updateOrderLine", 6,
"getOrderLines", 7
);

// Add the new buy/sell mark commands
commands.put("addBuySellMark", 8);
commands.put("removeBuySellMark", 9);
commands.put("updateBuySellMark", 10);
commands.put("getBuySellMarks", 11);

return commands;
}

@Override
Expand Down Expand Up @@ -204,6 +212,52 @@ public void receiveCommand(@Nonnull HTKLineContainerView containerView, String c
e.printStackTrace();
}
break;
case "addBuySellMark":
if (args != null && args.size() > 0) {
try {
ReadableMap buySellMarkData = args.getMap(0);
Map<String, Object> dataMap = buySellMarkData.toHashMap();
containerView.addBuySellMark(dataMap);
} catch (Exception e) {
android.util.Log.e("RNKLineView", "Error in addBuySellMark command", e);
e.printStackTrace();
}
} else {
}
break;
case "removeBuySellMark":
if (args != null && args.size() > 0) {
try {
String buySellMarkId = args.getString(0);
containerView.removeBuySellMark(buySellMarkId);
} catch (Exception e) {
android.util.Log.e("RNKLineView", "Error in removeBuySellMark command", e);
e.printStackTrace();
}
} else {
}
break;
case "updateBuySellMark":
if (args != null && args.size() > 0) {
try {
ReadableMap buySellMarkData = args.getMap(0);
Map<String, Object> dataMap = buySellMarkData.toHashMap();
containerView.updateBuySellMark(dataMap);
} catch (Exception e) {
android.util.Log.e("RNKLineView", "Error in updateBuySellMark command", e);
e.printStackTrace();
}
} else {
}
break;
case "getBuySellMarks":
try {
containerView.getBuySellMarks();
} catch (Exception e) {
android.util.Log.e("RNKLineView", "Error in getBuySellMarks command", e);
e.printStackTrace();
}
break;
default:
android.util.Log.w("RNKLineView", "Unknown command: " + commandId);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public class HTKLineContainerView extends RelativeLayout {
// Order line management
private Map<String, Map<String, Object>> orderLines = new HashMap<>();

// Buy/sell mark management - indexed by timestamp for O(1) lookup
private Map<Long, Map<String, Object>> buyMarks = new HashMap<>();
private Map<Long, Map<String, Object>> sellMarks = new HashMap<>();
private Map<String, Map<String, Object>> buySellMarks = new HashMap<>(); // Keep for compatibility

public HTKLineContainerView(ThemedReactContext context) {
super(context);
this.reactContext = context;
Expand Down Expand Up @@ -649,4 +654,168 @@ public Map<String, Map<String, Object>> getAllOrderLines() {
}
}

public void addBuySellMark(Map<String, Object> buySellMarkData) {

if (buySellMarkData == null || !buySellMarkData.containsKey("id") ||
!buySellMarkData.containsKey("time") || !buySellMarkData.containsKey("type")) {
return;
}

String id = (String) buySellMarkData.get("id");
long time = ((Number) buySellMarkData.get("time")).longValue();
String type = (String) buySellMarkData.get("type");

// Store in compatibility map
synchronized (buySellMarks) {
buySellMarks.put(id, buySellMarkData);
}

// Store in efficient lookup maps by timestamp
if ("buy".equals(type)) {
synchronized (buyMarks) {
buyMarks.put(time, buySellMarkData);
}
} else if ("sell".equals(type)) {
synchronized (sellMarks) {
sellMarks.put(time, buySellMarkData);
}
}

android.util.Log.d("HTKLineContainerView", "Added " + type + " mark with id: " + id + " at time: " + time);

// Trigger redraw to show the buy/sell mark
post(new Runnable() {
@Override
public void run() {
klineView.invalidate();
}
});
}

public void removeBuySellMark(String buySellMarkId) {

if (buySellMarkId == null || buySellMarkId.trim().isEmpty()) {
return;
}

// Find and remove from efficient lookup maps
Map<String, Object> markData;
synchronized (buySellMarks) {
markData = buySellMarks.get(buySellMarkId);
if (markData != null) {
buySellMarks.remove(buySellMarkId);
}
}

if (markData != null && markData.containsKey("time") && markData.containsKey("type")) {
long time = ((Number) markData.get("time")).longValue();
String type = (String) markData.get("type");

if ("buy".equals(type)) {
synchronized (buyMarks) {
buyMarks.remove(time);
}
} else if ("sell".equals(type)) {
synchronized (sellMarks) {
sellMarks.remove(time);
}
}
}

android.util.Log.d("HTKLineContainerView", "Removed buy/sell mark with id: " + buySellMarkId);

// Trigger redraw to remove the buy/sell mark
post(new Runnable() {
@Override
public void run() {
klineView.invalidate();
}
});
}

public void updateBuySellMark(Map<String, Object> buySellMarkData) {

if (buySellMarkData == null || !buySellMarkData.containsKey("id") ||
!buySellMarkData.containsKey("time") || !buySellMarkData.containsKey("type")) {
return;
}

String id = (String) buySellMarkData.get("id");
long time = ((Number) buySellMarkData.get("time")).longValue();
String type = (String) buySellMarkData.get("type");

// Remove old entry from efficient lookup maps if it exists
Map<String, Object> oldMarkData;
synchronized (buySellMarks) {
oldMarkData = buySellMarks.get(id);
buySellMarks.put(id, buySellMarkData);
}

if (oldMarkData != null && oldMarkData.containsKey("time") && oldMarkData.containsKey("type")) {
long oldTime = ((Number) oldMarkData.get("time")).longValue();
String oldType = (String) oldMarkData.get("type");

if ("buy".equals(oldType)) {
synchronized (buyMarks) {
buyMarks.remove(oldTime);
}
} else if ("sell".equals(oldType)) {
synchronized (sellMarks) {
sellMarks.remove(oldTime);
}
}
}

// Add to efficient lookup maps
if ("buy".equals(type)) {
synchronized (buyMarks) {
buyMarks.put(time, buySellMarkData);
}
} else if ("sell".equals(type)) {
synchronized (sellMarks) {
sellMarks.put(time, buySellMarkData);
}
}

android.util.Log.d("HTKLineContainerView", "Updated " + type + " mark with id: " + id + " at time: " + time);

// Trigger redraw to update the buy/sell mark
post(new Runnable() {
@Override
public void run() {
klineView.invalidate();
}
});
}

public List<Map<String, Object>> getBuySellMarks() {

// Return all buy/sell marks as a list
synchronized (buySellMarks) {
List<Map<String, Object>> buySellMarksList = new ArrayList<>(buySellMarks.values());
android.util.Log.d("HTKLineContainerView", "Returning " + buySellMarksList.size() + " buy/sell marks");
return buySellMarksList;
}
}

// Method to allow KLineChartView to access buy/sell marks for drawing
public Map<String, Map<String, Object>> getAllBuySellMarks() {
synchronized (buySellMarks) {
return new HashMap<>(buySellMarks);
}
}

// Efficient O(1) lookup methods for buy/sell marks by timestamp
public Map<String, Object> getBuyMarkForTime(long time) {
synchronized (buyMarks) {
return buyMarks.get(time);
}
}

public Map<String, Object> getSellMarkForTime(long time) {
synchronized (sellMarks) {
return sellMarks.get(time);
}
}

}
Loading