Skip to content

Instantly share code, notes, and snippets.

@ddovod
Created November 29, 2022 07:27
Show Gist options
  • Save ddovod/d43bb0efba10c6b611cf327c65ba02e6 to your computer and use it in GitHub Desktop.
Save ddovod/d43bb0efba10c6b611cf327c65ba02e6 to your computer and use it in GitHub Desktop.
Candlestick chart implementation using implot
#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();
}
}
}
#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