Created
November 29, 2022 07:27
-
-
Save ddovod/d43bb0efba10c6b611cf327c65ba02e6 to your computer and use it in GitHub Desktop.
Candlestick chart implementation using implot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <imgui.h> | |
#include <implot.h> | |
#include <implot_internal.h> | |
namespace | |
{ | |
static const auto bullCol = ImVec4(0.2f, 0.8f, 0.3f, 0.75f); | |
static const auto bearCol = ImVec4(0.8f, 0.2f, 0.3f, 0.75f); | |
} | |
namespace ImPlot | |
{ | |
int BinarySearch(const double* arr, int l, int r, double x, int count, int offset, int stride) | |
{ | |
if (r >= l) { | |
int mid = l + (r - l) / 2; | |
double value = OffsetAndStride(arr, mid, count, offset, stride); | |
if (value == x) { | |
return mid; | |
} | |
if (value > x) { | |
return BinarySearch(arr, l, mid - 1, x, count, offset, stride); | |
} | |
return BinarySearch(arr, mid + 1, r, x, count, offset, stride); | |
} | |
return -1; | |
} | |
inline tm* GetTime(const ImPlotTime& t, tm* ptm) | |
{ | |
if (GetStyle().UseLocalTime) { | |
return GetLocTime(t,ptm); | |
} | |
return GetGmtTime(t,ptm); | |
} | |
int FormatTime(const ImPlotTime& t, char* buffer, int size) | |
{ | |
tm& Tm = GImPlot->Tm; | |
ImPlot::GetTime(t, &Tm); | |
const int ms = t.Us / 1000; | |
const int sec = Tm.tm_sec; | |
const int min = Tm.tm_min; | |
const int hr = Tm.tm_hour; | |
const int day = Tm.tm_mday; | |
const int mon = Tm.tm_mon + 1; | |
const int year = Tm.tm_year + 1900; | |
return snprintf(buffer, size, "%04d-%02d-%02d %02d:%02d:%02d.%03d", year, mon, day, hr, min, sec, ms); | |
} | |
int FormatTimeHourMin(const ImPlotTime& t, char* buffer, int size) | |
{ | |
tm& Tm = GImPlot->Tm; | |
ImPlot::GetTime(t, &Tm); | |
const int min = Tm.tm_min; | |
const int hr = Tm.tm_hour; | |
return snprintf(buffer, size, "%02d:%02d", hr, min); | |
} | |
std::optional<double> PlotCandlestickChart(const char* label_id, const double* xs, const double* opens, const double* closes, const double* lows, const double* highs, int count, int offset, int stride, bool tooltip) | |
{ | |
std::optional<double> result; | |
// get ImGui window DrawList | |
ImDrawList* draw_list = ImPlot::GetPlotDrawList(); | |
// calc real value width | |
double half_width = count > 1 ? (xs[1] - xs[0]) * 0.25 : 0.25; | |
// custom tool | |
if (ImPlot::IsPlotHovered() && tooltip) { | |
ImPlotPoint mouse = ImPlot::GetPlotMousePos(); | |
mouse.x = ImPlot::RoundTime(ImPlotTime::FromDouble(mouse.x), ImPlotTimeUnit_Min).ToDouble(); | |
// find mouse location index | |
int idx = BinarySearch(xs, 0, count - 1, mouse.x, count, offset, stride); | |
// render tool tip (won't be affected by plot clip rect) | |
if (idx != -1) { | |
float tool_l = ImPlot::PlotToPixels(mouse.x - half_width * 1.3, mouse.y).x; | |
float tool_r = ImPlot::PlotToPixels(mouse.x + half_width * 1.3, mouse.y).x; | |
float tool_t = ImPlot::GetPlotPos().y; | |
float tool_b = tool_t + ImPlot::GetPlotSize().y; | |
char buff[32]; | |
const auto t = OffsetAndStride(xs, idx, count, offset, stride); | |
FormatTimeHourMin(ImPlotTime::FromDouble(t), buff, 32); | |
ImPlot::PushPlotClipRect(); | |
const auto& id = ImHashStr((label_id + std::string("_candle_") + std::to_string(idx)).c_str()); | |
ImGui::ItemAdd({ImVec2(tool_l, tool_t), ImVec2(tool_r, tool_b)}, id); | |
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && ImGui::IsItemHovered(ImGuiHoveredFlags_None)) { | |
result = t; | |
} | |
draw_list->AddRectFilled(ImVec2(tool_l, tool_t), ImVec2(tool_r, tool_b), ImColor(1.0f, 1.0f, 1.0f, 0.1f)); | |
ImPlot::PopPlotClipRect(); | |
const auto o = OffsetAndStride(opens, idx, count, offset, stride); | |
const auto h = OffsetAndStride(highs, idx, count, offset, stride); | |
const auto l = OffsetAndStride(lows, idx, count, offset, stride); | |
const auto c = OffsetAndStride(closes, idx, count, offset, stride); | |
ImGui::BeginTooltip(); | |
ImGui::Text("Time: %s", buff); | |
ImGui::Separator(); | |
ImGui::Text("Open: $%.2f", o); | |
ImGui::Text("High: $%.2f", h); | |
ImGui::Text("Low: $%.2f", l); | |
ImGui::Text("Close: $%.2f", c); | |
ImGui::EndTooltip(); | |
} | |
} | |
// begin plot item | |
if (ImPlot::BeginItem(label_id)) { | |
// override legend icon color | |
ImPlot::GetCurrentItem()->Color = ImVec4(1,1,1,1); | |
// render data | |
for (int i = 0; i < count; ++i) { | |
const auto x = OffsetAndStride(xs, i, count, offset, stride); | |
const auto o = OffsetAndStride(opens, i, count, offset, stride); | |
const auto h = OffsetAndStride(highs, i, count, offset, stride); | |
const auto l = OffsetAndStride(lows, i, count, offset, stride); | |
const auto c = OffsetAndStride(closes, i, count, offset, stride); | |
ImVec2 open_pos = ImPlot::PlotToPixels(x - half_width, o); | |
ImVec2 close_pos = ImPlot::PlotToPixels(x + half_width, c); | |
ImVec2 low_pos = ImPlot::PlotToPixels(x, l); | |
ImVec2 high_pos = ImPlot::PlotToPixels(x, h); | |
ImU32 color = ImGui::GetColorU32(o > c ? bearCol : bullCol); | |
if (abs(h - l) > FLT_EPSILON) { | |
draw_list->AddLine(low_pos, high_pos, color); | |
} | |
if (abs(o - c) > FLT_EPSILON) { | |
draw_list->AddRectFilled(open_pos, close_pos, color); | |
} else { | |
draw_list->AddLine(open_pos, close_pos, color); | |
} | |
} | |
// end plot item | |
ImPlot::EndItem(); | |
} | |
return result; | |
} | |
void PlotCandlestickChart(const char* label_id, int x_begin, const double* opens, const double* closes, const double* lows, const double* highs, int count, int offset, int stride) | |
{ | |
ImDrawList* draw_list = ImPlot::GetPlotDrawList(); | |
double half_width = 0.25; | |
// begin plot item | |
if (ImPlot::BeginItem(label_id)) { | |
// override legend icon color | |
ImPlot::GetCurrentItem()->Color = ImVec4(1,1,1,1); | |
// render data | |
for (int i = 0; i < count; ++i) { | |
const auto x = static_cast<double>(i) + x_begin; | |
const auto o = OffsetAndStride(opens, i, count, offset, stride); | |
const auto h = OffsetAndStride(highs, i, count, offset, stride); | |
const auto l = OffsetAndStride(lows, i, count, offset, stride); | |
const auto c = OffsetAndStride(closes, i, count, offset, stride); | |
ImVec2 open_pos = ImPlot::PlotToPixels(x - half_width, o); | |
ImVec2 close_pos = ImPlot::PlotToPixels(x + half_width, c); | |
ImVec2 low_pos = ImPlot::PlotToPixels(x, l); | |
ImVec2 high_pos = ImPlot::PlotToPixels(x, h); | |
ImU32 color = ImGui::GetColorU32(o > c ? bearCol : bullCol); | |
if (abs(h - l) > FLT_EPSILON) { | |
draw_list->AddLine(low_pos, high_pos, color); | |
} | |
if (abs(o - c) > FLT_EPSILON) { | |
draw_list->AddRectFilled(open_pos, close_pos, color); | |
} else { | |
draw_list->AddLine(open_pos, close_pos, color); | |
} | |
} | |
// end plot item | |
ImPlot::EndItem(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <optional> | |
namespace ImPlot | |
{ | |
std::optional<double> PlotCandlestickChart(const char* label_id, const double* xs, const double* opens, const double* closes, const double* lows, const double* highs, int count, int offset = 0, int stride = sizeof(double), bool tooltip = false); | |
void PlotCandlestickChart(const char* label_id, int x_begin, const double* opens, const double* closes, const double* lows, const double* highs, int count, int offset = 0, int stride = sizeof(double)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment