Skip to content

Add 1D line plot view#27

Open
cmorency1 wants to merge 1 commit into
SEATStandards:mainfrom
cmorency1:1D-line-plot
Open

Add 1D line plot view#27
cmorency1 wants to merge 1 commit into
SEATStandards:mainfrom
cmorency1:1D-line-plot

Conversation

@cmorency1
Copy link
Copy Markdown

When doing a shift+left click on the 2D map, a 1D plot pops up. There is an "Export to PNG" button to plot the 1D figure.
Two new files were created: wxNcVisLinePlot.cpp & wxNcVisLinePlot.h

@rljacob rljacob requested a review from Copilot March 18, 2026 23:33
@rljacob rljacob self-assigned this Mar 18, 2026
@rljacob rljacob added the enhancement New feature or request label Mar 18, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an interactive 1D “line plot” window that can be opened from the 2D map via Shift+left-click, with an option to export the plot as a PNG.

Changes:

  • Introduces wxNcVisLinePlotFrame/Panel to render a simple 1D line plot and export it as PNG.
  • Adds Shift+left-click handling in wxImagePanel to request a time series at the clicked lat/lon and show/update the plot window.
  • Implements time-series extraction logic in wxNcVisFrame and initializes wxWidgets PNG image handlers at app startup.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/wxNcVisLinePlot.h Declares the line plot panel + frame and export API.
src/wxNcVisLinePlot.cpp Implements plot rendering, tick labeling, and PNG export UI.
src/wxNcVisFrame.h Declares time-series callback, extraction helper, and plot-frame pointer.
src/wxNcVisFrame.cpp Implements time/lat/lon dimension detection, time-series extraction, and plot-window lifecycle.
src/wxImagePanel.h Declares Shift+left-click mouse handler.
src/wxImagePanel.cpp Dispatches Shift+left-click to wxNcVisFrame::OnShiftClickTimeSeries.
src/ncvis.cpp Adds PNG handler initialization for image saving.
src/CMakeLists.txt Adds wxNcVisLinePlot.cpp to build sources.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/wxNcVisLinePlot.cpp
Comment on lines +285 to +299
wxBitmap bmp(sz.x, sz.y);
wxMemoryDC memdc(bmp);
memdc.SetBackground(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)));
memdc.Clear();

m_panel->Refresh();
m_panel->Update();

wxClientDC srcdc(m_panel);
memdc.Blit(0, 0, sz.x, sz.y, &srcdc, 0, 0);

memdc.SelectObject(wxNullBitmap);

if (!m_panel->ExportToPNG(dlg.GetPath(), m_panel->GetClientSize().x, m_panel->GetClientSize().y)) {
wxMessageBox("Failed to save PNG.", "Export PNG");
Comment thread src/wxNcVisLinePlot.cpp
Comment on lines +34 to +46
wxString wxNcVisLinePlotPanel::FormatXValue(double xv) const {
if (m_baseEpoch == 0) {
return wxString::Format("%.4g", xv);
}

time_t t = m_baseEpoch + static_cast<time_t>(std::llround(xv * 86400.0));
wxDateTime dt(t);

if (!dt.IsValid()) {
return wxString::Format("%.4g", xv);
}

return dt.Format("%Y-%m-%d");
Comment thread src/wxNcVisFrame.cpp
Comment on lines +1374 to +1380
if (m_pLinePlot == NULL) {
m_pLinePlot = new wxNcVisLinePlotFrame(this, title, vecTime, vecValue, timeUnits, baseEpoch, yLabel);
m_pLinePlot->Show();
} else {
m_pLinePlot->UpdatePlot(title, vecTime, vecValue, timeUnits, baseEpoch, yLabel);
if (!m_pLinePlot->IsShown()) m_pLinePlot->Show();
}
Comment thread src/wxNcVisFrame.cpp
Comment on lines +1319 to +1345
for (long it = 0; it < nT; ++it) {
cur[(int)lTimeDim] = it;

bool ok = false;
if (nDims == 3) {
ok = m_varActive->set_cur(cur[0], cur[1], cur[2]);
} else if (nDims == 4) {
ok = m_varActive->set_cur(cur[0], cur[1], cur[2], cur[3]);
} else if (nDims == 5) {
ok = m_varActive->set_cur(cur[0], cur[1], cur[2], cur[3], cur[4]);
} else {
return false;
}
if (!ok) return false;

float val = std::numeric_limits<float>::quiet_NaN();

if (nDims == 3) {
if (!m_varActive->get(&val, 1, 1, 1)) return false;
} else if (nDims == 4) {
if (!m_varActive->get(&val, 1, 1, 1, 1)) return false;
} else if (nDims == 5) {
if (!m_varActive->get(&val, 1, 1, 1, 1, 1)) return false;
}

vecValue[it] = val;
}
Comment thread src/wxImagePanel.cpp
const double dLon = m_dSampleX[pos.x];
const double dLat = m_dSampleY[m_dSampleY.size() - pos.y - 1];

_ASSERT(m_pncvisparent != NULL);
Comment thread src/wxNcVisFrame.cpp
Comment on lines +1110 to +1116
std::string name = pDim->name();
std::transform(name.begin(), name.end(), name.begin(), ::tolower);

if ((name == "time") ||
(name == "times") ||
(name == "t") ||
(name.find("time") != std::string::npos))
Comment thread src/wxNcVisLinePlot.cpp
Comment on lines +75 to +188
if (sz.x < 80 || sz.y < 80) return;
if (m_x.size() < 2 || m_y.size() < 2) return;

// Padding tuned for titles + range labels
const int padL = 85, padR = 60, padT = 35, padB = 85;
const int w = sz.x - padL - padR;
const int h = sz.y - padT - padB;
if (w <= 10 || h <= 10) return;

// Plot rectangle (white box)
wxRect plotRect(padL, padT, w, h);
dc.SetBrush(*wxWHITE_BRUSH);
dc.SetPen(*wxBLACK_PEN);
dc.DrawRectangle(plotRect);

// Bounds
auto mm = std::minmax_element(m_x.begin(), m_x.end());
double xmin = *(mm.first);
double xmax = *(mm.second);

float ymin = m_y[0], ymax = m_y[0];
for (float v : m_y) {
ymin = std::min(ymin, v);
ymax = std::max(ymax, v);
}

// Avoid zero ranges
if (xmax == xmin) xmax = xmin + 1.0;
if (ymax == ymin) ymax = ymin + 1.0f;

auto X = [&](double xv) -> int {
return padL + (int)std::lround((xv - xmin) * (double)w / (xmax - xmin));
};
auto Y = [&](float yv) -> int {
return padT + (int)std::lround((double)(ymax - yv) * (double)h / (double)(ymax - ymin));
};

const int nXTicks = 6;
const int nYTicks = 6;

// Grid lines
dc.SetPen(wxPen(wxColour(180,180,180), 1, wxPENSTYLE_DOT));

for (int i = 0; i < nXTicks; ++i) {
double xv = xmin + (xmax - xmin) * static_cast<double>(i) / static_cast<double>(nXTicks - 1);
int px = X(xv);
dc.DrawLine(px, padT, px, padT + h);
}

for (int i = 0; i < nYTicks; ++i) {
double yv = ymin + (ymax - ymin) * static_cast<double>(i) / static_cast<double>(nYTicks - 1);
int py = Y(static_cast<float>(yv));
dc.DrawLine(padL, py, padL + w, py);
}

// Redraw border on top of grid
dc.SetPen(*wxBLACK_PEN);
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(plotRect);

// Title (top)
wxFont fontTitle = dc.GetFont();
fontTitle.SetWeight(wxFONTWEIGHT_BOLD);
fontTitle.SetPointSize(fontTitle.GetPointSize() + 2);
dc.SetFont(fontTitle);

wxSize titleSz = dc.GetTextExtent(m_title);
dc.DrawText(m_title, padL + (w - titleSz.x) / 2, 8);

wxFont fontNormal = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
dc.SetFont(fontNormal);

// Axis titles
wxSize xLabSz = dc.GetTextExtent(m_xLabel);
dc.DrawText(m_xLabel, padL + (w - xLabSz.x) / 2, padT + h + 40);

// Rotated Y label on left
// (90 degrees puts it reading bottom-to-top; use 270 if you prefer top-to-bottom)
dc.DrawRotatedText(m_yLabel, 15, padT + h/2 + dc.GetTextExtent(m_yLabel).x/2, 90.0);

// Axis range labels (min/max)
for (int i = 0; i < nXTicks; ++i) {
double xv = xmin + (xmax - xmin) * static_cast<double>(i) / static_cast<double>(nXTicks - 1);
int px = X(xv);

// tick mark
dc.SetPen(*wxBLACK_PEN);
dc.DrawLine(px, padT + h, px, padT + h + 5);

// label
wxString s = FormatXValue(xv);
wxSize tsz = dc.GetTextExtent(s);
dc.DrawText(s, px - tsz.x / 2, padT + h + 12);
}

for (int i = 0; i < nYTicks; ++i) {
double yv = ymin + (ymax - ymin) * static_cast<double>(i) / static_cast<double>(nYTicks - 1);
int py = Y(static_cast<float>(yv));

// tick mark
dc.SetPen(*wxBLACK_PEN);
dc.DrawLine(padL - 5, py, padL, py);

// label
wxString s = wxString::Format("%.4g", yv);
wxSize tsz = dc.GetTextExtent(s);
dc.DrawText(s, padL - 10 - tsz.x, py - tsz.y / 2);
}

// Line plot
dc.SetPen(*wxBLACK_PEN);
wxPoint prev(X(m_x[0]), Y(m_y[0]));
for (size_t i = 1; i < m_x.size(); ++i) {
wxPoint cur(X(m_x[i]), Y(m_y[i]));
Comment thread src/wxNcVisLinePlot.cpp
Comment on lines +209 to +215
wxSize oldSize = GetClientSize();
SetSize(width, height);

DrawPlot(dc, wxSize(width, height));

SetSize(oldSize);

Comment thread src/wxNcVisFrame.cpp
// ------------------------------------------------------------
vecValue.resize(nT);

std::vector<long> cur(nDims, 0);
Comment thread src/wxNcVisFrame.cpp
Comment on lines +1292 to +1305
int Y=0,M=0,D=0,h=0,m=0,sec=0;
int nn = std::sscanf(rest.c_str(), "%d-%d-%d %d:%d:%d", &Y,&M,&D,&h,&m,&sec);
if (nn >= 3) {
std::tm tmv;
std::memset(&tmv, 0, sizeof(tmv));
tmv.tm_year = Y - 1900;
tmv.tm_mon = M - 1;
tmv.tm_mday = D;
tmv.tm_hour = (nn >= 4) ? h : 0;
tmv.tm_min = (nn >= 5) ? m : 0;
tmv.tm_sec = (nn >= 6) ? sec : 0;

outBaseEpoch = std::mktime(&tmv);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants