Add 1D line plot view#27
Open
cmorency1 wants to merge 1 commit into
Open
Conversation
Contributor
There was a problem hiding this comment.
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/Panelto render a simple 1D line plot and export it as PNG. - Adds Shift+left-click handling in
wxImagePanelto request a time series at the clicked lat/lon and show/update the plot window. - Implements time-series extraction logic in
wxNcVisFrameand 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 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 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 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 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; | ||
| } |
| const double dLon = m_dSampleX[pos.x]; | ||
| const double dLat = m_dSampleY[m_dSampleY.size() - pos.y - 1]; | ||
|
|
||
| _ASSERT(m_pncvisparent != NULL); |
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 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 on lines
+209
to
+215
| wxSize oldSize = GetClientSize(); | ||
| SetSize(width, height); | ||
|
|
||
| DrawPlot(dc, wxSize(width, height)); | ||
|
|
||
| SetSize(oldSize); | ||
|
|
| // ------------------------------------------------------------ | ||
| vecValue.resize(nT); | ||
|
|
||
| std::vector<long> cur(nDims, 0); |
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); | ||
| } |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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