diff --git a/openspec/changes/fix-jessx-deal-timestamps/.openspec.yaml b/openspec/changes/fix-jessx-deal-timestamps/.openspec.yaml
new file mode 100644
index 000000000..878dc3156
--- /dev/null
+++ b/openspec/changes/fix-jessx-deal-timestamps/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-08-07
diff --git a/openspec/changes/fix-jessx-deal-timestamps/design.md b/openspec/changes/fix-jessx-deal-timestamps/design.md
new file mode 100644
index 000000000..261ce87e5
--- /dev/null
+++ b/openspec/changes/fix-jessx-deal-timestamps/design.md
@@ -0,0 +1,54 @@
+## Context
+
+- See `proposal.md` for motivation and `specs/jessx-time-handling/spec.md` for the behavioral requirements.
+- JESSX sends deal/order timestamps as *elapsed milliseconds within the current period* (`ExperimentManager.getTimeInPeriod()` = `now - periodBeginning`). EclipseTrader currently feeds them straight into `new Date(...)`, producing 1970 dates.
+- Two consumption points in `org.eclipsetrader.jessx`:
+ 1. `BrokerConnector` — streaming `Deal` → `Trade` + OHLC history (this is what the closed PR #18 patched inline with a sub-2000 heuristic).
+ 2. `JessxTradeHistory` — persisted holding `PURCHASE_DATE` (still broken, unchanged by PR #18).
+
+## Goals / Non-Goals
+
+**Goals:**
+- Absolute, current-era dates for JESSX deals/orders at both consumption points.
+- One shared conversion so streaming charts and persisted holdings can never diverge.
+- Supersede PR #18's inline workaround.
+
+**Non-Goals:**
+- Changing what the JESSX server sends (the server is correct to send elapsed time; it is the client's interpretation that is wrong).
+- Reconstructing the exact simulated wall-clock time from period start (live deals arrive in real time; arrival time is accurate enough).
+- Migrating already-persisted 1970 holdings from old repositories.
+
+## Decisions
+
+### 1. Single shared converter with a magnitude-based interpretation
+
+Add one helper (e.g., `JessxTime.toAbsoluteDate(long timestamp)` in a new internal utility class) used by both `BrokerConnector` and `JessxTradeHistory`:
+
+- **Below threshold** (elapsed-in-period value, bounded by period duration, e.g. `< 86,400,000 ms` — one day): interpret as elapsed → return arrival time (`new Date(System.currentTimeMillis())`).
+- **At or above threshold** (a plausible epoch millis): return `new Date(timestamp)` unchanged.
+
+The threshold is the maximum plausible period duration. Unlike PR #18's "before year 2000" check, this correctly preserves genuine epoch values while catching all realistic elapsed values (which are at most a few hours, i.e. millions of ms, never billions).
+
+- **Rationale**: deals are received live, so `arrival time ≈ periodStart + elapsed` within network latency. Tracking period start on the client would add state and message plumbing for no observable gain.
+- **Alternative considered**: track `periodBeginning` on the client and compute `periodStart + elapsed`. Rejected — requires new state and event handling across the feed, and its output equals arrival time in practice.
+
+### 2. Apply at both consumption points
+
+- `BrokerConnector` Deal handling: replace the inline PR #18 heuristic with `JessxTime.toAbsoluteDate(...)`.
+- `JessxTradeHistory`: route `finalDeal.getTimestamp()` through the same converter before `IPropertyConstants.PURCHASE_DATE` is set.
+
+### 3. Keep OHLC bar-merging behavior as-is
+
+The existing merge condition (`last.getDate().equals(tradeData.getTime())`) is left untouched. Consecutive deals get distinct arrival timestamps (ms precision), so each becomes its own bar — this matches current behavior and satisfies the spec's "current-era date" requirement.
+
+## Risks / Trade-offs
+
+- **[Risk] Threshold heuristic misclassifies an extreme value** → an elapsed period longer than one day would be misread as epoch. Mitigation: threshold is a single named constant, documented; simulation period durations in JESSX are far smaller. Can be tuned without API change.
+- **[Risk] Arrival time instead of true sim time on persisted holdings** → purchase dates are "when the platform received the deal", not the simulated timestamp. Accepted for a live simulation; consistent between chart and holdings (which is what the spec requires).
+- **[Risk] Pre-existing 1970 holdings remain in repositories** → out of scope; only new deals are corrected. Flagged in the migration plan so it is not mistaken for a regression.
+
+## Migration Plan
+
+- No data migration. Existing broken holdings stay as-is (documented limitation); newly saved deals get correct dates.
+- Rollback: revert the change — the converter is additive and localized to the two call sites.
+- This change is independent of `modernize-chart-rendering` and of the extracted PR #18 bug fixes.
diff --git a/openspec/changes/fix-jessx-deal-timestamps/proposal.md b/openspec/changes/fix-jessx-deal-timestamps/proposal.md
new file mode 100644
index 000000000..52daa39dd
--- /dev/null
+++ b/openspec/changes/fix-jessx-deal-timestamps/proposal.md
@@ -0,0 +1,28 @@
+## Why
+
+The JESSX trading game sends deal/order timestamps as elapsed milliseconds within the current period (`ExperimentManager.getTimeInPeriod()` = `now - periodBeginning`), not epoch milliseconds. EclipseTrader misreads them as epoch millis and feeds them into `new Date(...)`, so streaming trades and persisted holdings show 1970 dates on charts. The closed PR #18 patched only the streaming path in `BrokerConnector` with a "before year 2000 → use now" heuristic; the persistence path (`JessxTradeHistory`) still writes 1970 purchase dates.
+
+## What Changes
+
+- Introduce a single, shared interpretation of JESSX deal/order timestamps so elapsed-in-period values are converted to absolute dates instead of being misread as epoch millis.
+- Apply the conversion consistently at both consumption points:
+ - `BrokerConnector` streaming trade feed (charts/OHLC history).
+ - `JessxTradeHistory` persisted holdings (portfolio purchase dates).
+- Absorb and replace the PR #18 workaround; the heuristic moves into the shared converter (or is superseded by a cleaner derivation) rather than living inline in `BrokerConnector`.
+
+## Capabilities
+
+### New Capabilities
+
+- `jessx-time-handling`: how JESSX deal/order timestamps are interpreted and converted to absolute times, applied consistently across the streaming feed and persisted trade history.
+
+### Modified Capabilities
+
+None — this repo has no existing specs yet; this capability is new.
+
+## Impact
+
+- **Code**: `org.eclipsetrader.jessx` — `BrokerConnector` (streaming `Deal` handling) and `JessxTradeHistory` (holding persistence). A shared timestamp converter utility.
+- **Behavior**: charts no longer show 1970 timestamps; portfolio holdings get correct purchase dates. No change for non-JESSX data feeds.
+- **Dependencies**: none new.
+- **Related work**: independent of the chart modernization change; this stays out of the merged PR #18 bug-fix extraction.
diff --git a/openspec/changes/fix-jessx-deal-timestamps/specs/jessx-time-handling/spec.md b/openspec/changes/fix-jessx-deal-timestamps/specs/jessx-time-handling/spec.md
new file mode 100644
index 000000000..c4cbb9d31
--- /dev/null
+++ b/openspec/changes/fix-jessx-deal-timestamps/specs/jessx-time-handling/spec.md
@@ -0,0 +1,42 @@
+## Purpose
+
+Defines how JESSX deal and order timestamps are interpreted and converted to absolute dates, consistently across streaming charts and persisted trade history.
+
+## ADDED Requirements
+
+### Requirement: Absolute deal timestamps
+
+JESSX deal and order timestamps that represent elapsed time within the current period SHALL be converted to absolute dates when consumed by the platform, so trades and OHLC bars carry real-world dates.
+
+#### Scenario: Live deal with elapsed timestamp
+
+- **WHEN** a JESSX Deal message carries an elapsed-in-period timestamp
+- **THEN** the resulting trade and OHLC bar carry an absolute date near the time the deal was received rather than the epoch (1970)
+
+#### Scenario: Already-absolute timestamp
+
+- **WHEN** a JESSX message carries a value that is already a valid absolute epoch millis
+- **THEN** the value is used as-is without conversion
+
+### Requirement: Consistent interpretation across consumption paths
+
+The same timestamp interpretation SHALL apply to the streaming trade feed and to persisted trade history.
+
+#### Scenario: Persisted holding purchase date
+
+- **WHEN** a JESSX deal is saved to the repository as a holding
+- **THEN** the purchase date is an absolute date consistent with the same deal's streaming chart time
+
+#### Scenario: Chart and holdings agree
+
+- **WHEN** the same JESSX deal appears both on a chart and in the portfolio holdings
+- **THEN** both show the same absolute timestamp
+
+### Requirement: No epoch dates from elapsed timestamps
+
+The platform SHALL NOT display or persist dates derived from JESSX elapsed-in-period timestamps as 1970 epoch dates.
+
+#### Scenario: Chart shows current-era date
+
+- **WHEN** a JESSX deal is shown on a chart
+- **THEN** the tooltip and summary bar show a date in the current era, not 1970
diff --git a/openspec/changes/fix-jessx-deal-timestamps/tasks.md b/openspec/changes/fix-jessx-deal-timestamps/tasks.md
new file mode 100644
index 000000000..0650f04e1
--- /dev/null
+++ b/openspec/changes/fix-jessx-deal-timestamps/tasks.md
@@ -0,0 +1,12 @@
+## 1. Shared timestamp converter
+
+- [x] 1.1 Add `JessxTime.toAbsoluteDate(long)` in the jessx bundle's internal package: values below the elapsed threshold (one-day constant) resolve to the arrival time, values at or above it are treated as epoch millis
+- [x] 1.2 Replace the inline sub-2000 heuristic in `BrokerConnector` Deal handling with the shared converter
+- [x] 1.3 Route `JessxTradeHistory` holding `PURCHASE_DATE` through the same converter
+- [x] 1.4 Add unit tests for the converter (elapsed value → current-era date, epoch value → unchanged, boundary at the threshold) wired into the Maven reactor so they run in GitHub Actions CI
+
+## 2. Verification
+
+- [x] 2.1 Run `mvn package` on the branch (GitHub Actions `maven.yml` on JDK 21) and confirm `org.eclipsetrader.jessx` compiles and converter tests pass
+- [ ] 2.2 Live-check in the running product (Codespaces virtual display or a local machine): start a JESSX simulation and confirm chart tooltips/summary show current-era timestamps, not 1970
+- [ ] 2.3 Confirm a traded deal persists with a current-era purchase date in the portfolio holdings, and that chart and holdings agree (spec `jessx-time-handling`)
diff --git a/openspec/changes/modernize-chart-rendering/.openspec.yaml b/openspec/changes/modernize-chart-rendering/.openspec.yaml
new file mode 100644
index 000000000..878dc3156
--- /dev/null
+++ b/openspec/changes/modernize-chart-rendering/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-08-07
diff --git a/openspec/changes/modernize-chart-rendering/design.md b/openspec/changes/modernize-chart-rendering/design.md
new file mode 100644
index 000000000..1b6fd260c
--- /dev/null
+++ b/openspec/changes/modernize-chart-rendering/design.md
@@ -0,0 +1,69 @@
+## Context
+
+- See `proposal.md` for motivation. See `specs/chart-rendering/spec.md` and `specs/chart-theme/spec.md` for the behavioral requirements this design satisfies.
+- The chart renders by drawing onto an offscreen `Image` (`ChartCanvas.onPaint`, `DateScaleCanvas`, vertical scale canvas), then blits it to the canvas. The image is recreated only on resize, and `Image.getBounds()` returns *logical* size, so a zoom change alone never triggers recreation — an image created at one zoom stays stale when the device zoom changes (mixed-DPI monitors, OS scale changes).
+- Chart colors are inline `new RGB(...)` literals scattered across the charts package, `MainChartFactory`, and `MainPropertiesPage`.
+- Geometry is partially cached already (`valid`/`pointArray` pooling in `CandleStickChart`, etc.), but `setDataBounds` re-filters the full series O(n) and there is no downsampling, so zoomed-out large histories draw every point.
+- Target platform is **Eclipse 2024-03 / SWT 4.31, Java 21** (see `org.eclipsetrader.releng/eclipsetrader.target`). On SWT 4.31, `new Image(device, w, h)` already creates a device-zoom-aware backing image: width/height are logical points auto-scaled to native pixels in the constructor, and `getBounds()` reports logical size. Per-monitor zoom is `Monitor.getZoom()`; the zoom captured at image creation is `DPIUtil.getDeviceZoom()`. `GC.setAntialias()`/`GC.setTextAntialias()` are available. (`Display.getZoom()` and `Image.setScaleFactor()` are **not** in 4.31 — they landed in SWT 3.104 / 2024-09, one release after the target.)
+
+## Goals / Non-Goals
+
+**Goals:**
+- Crisp chart output on any display zoom (HiDPI) with minimal change to chart-object drawing code.
+- One source of truth for chart colors, with user-saved color preferences still winning.
+- Bounded rendering work per frame regardless of history size.
+
+**Non-Goals:**
+- Dark mode UI / theme switching in the preferences UI (the theme layer enables it later; selecting themes is out of scope).
+- Rewriting the `IChartObject` model or the axis classes.
+- Overhauling gridline/`Calendar`-based tick computation (`ChartCanvas.paintBackground`) — tracked as a follow-up, not in this change.
+- Changing chart templates or persisted layouts.
+
+## Decisions
+
+### 1. HiDPI via zoom-aware offscreen images
+
+Create offscreen images at the chart's logical `clientArea` size with `new Image(display, w, h)`. SWT 4.31's image/GC pipeline is already device-zoom-aware: the constructor auto-scales the backing store to native pixels, `getBounds()` reports logical size, and GC drawing auto-scales logical coordinates. All existing drawing code (which uses logical coordinates) keeps working unchanged and already lands at native resolution.
+
+The remaining defect is lifecycle, not size: canvases recreate the image only on resize, and because `getBounds()` returns logical size the resize check never fires on a zoom change. Fix with a single helper, `ChartUtils.createBackingImage(Canvas, Rectangle)`, that (a) creates the logical-size image and (b) records the zoom at creation — from the canvas's monitor via `Monitor.getZoom()`, falling back to `display.getDPI().x / 72` if unavailable — and lets the canvas detect on the next paint that the zoom differs and recreate the image. Used by the main canvas, the date scale canvas, and the vertical scale canvas.
+
+- **Alternative considered**: create the image at `clientArea * zoom/100` pixels and apply `image.setScaleFactor(zoom, zoom)`. Rejected: `Image.setScaleFactor` does not exist in SWT 4.31 (added in 3.104/2024-09), and the 4.31 `Image(Device, w, h)` constructor already auto-scales — multiplying by zoom again would double-scale at zoom > 100%.
+- **Alternative considered**: `GC.setTransform(new Transform(...scale))` per paint. Rejected: more invasive, risks double-scaling on every draw call, and doesn't help the blit step.
+- **Alternative considered**: keep resize-only recreation (status quo). Rejected — that is the stale-zoom defect this change fixes.
+- Antialiasing (shapes + text) is enabled in the `Graphics` constructor, as proposed in the superseded PR.
+
+### 2. Theme layer: `ChartTheme` value object + default registry
+
+Introduce an immutable `ChartTheme` (RGB: line, positive, negative, outline, grid, background) and a `ChartThemes` holder exposing the default light theme (the Material palette proposed in PR #18: blue line `33,150,243`, teal `38,166,154`, red `239,83,80`, outline `64,64,64`).
+
+- Chart classes (`CandleStickChart`, `BarChart`, `HistogramBarChart`, `HistogramAreaChart`, `OHLCLineChart`, `LineChart`) replace their inline `new RGB(...)` field initializers/constructor fallbacks with lookups from `ChartThemes.getDefault()`. Their RGB constructor parameters stay — the `null`-fallback semantics are unchanged.
+- `MainChartFactory` field defaults and `MainPropertiesPage` color-selector defaults read from the same theme.
+- User preferences keep their existing path: `setParameters` → non-null RGB → `createObject(...)` passes them in, overriding theme defaults (spec `chart-theme`).
+- **Alternative considered**: full dependency injection / OSGi service for themes. Rejected as over-engineering for this codebase; a plain value object + static default is enough to centralize the palette and later add a second theme.
+- **Rationale for value-object over a live theme object**: RGB is a plain value in SWT; no listeners/observability are needed for this change.
+
+### 3. Downsampling via per-renderer aggregators, cached per visible range
+
+Extend the existing `setDataBounds`/`valid` pattern with an aggregation step and a cache keyed by `(firstDate, lastDate, pixelWidth)`:
+
+- When the visible points exceed `clientArea.width`, aggregate before building geometry.
+- **OHLC renderers** (candles, bars, OHLC line): min/max binning per pixel column — preserve each column's high/low (and first-open/last-close for candles) so zoomed-out bars stay honest.
+- **Scalar renderers** (line, area, histogram): min/max per column as well (keeps spikes visible); LTTB is an acceptable alternative for line aesthetics — implementation detail, spec only requires one aggregate per column.
+- Implemented as shared helpers (e.g., `OHLCDownsampler.downsample(IOHLC[], int width)` and a scalar counterpart) so all chart classes benefit without duplicating logic.
+- Cache lives with the chart object alongside `pointArray`; `invalidate()` clears it. Work per frame becomes O(pixels) to draw plus O(n) only when data/bounds actually change.
+- **Alternative considered**: always-on LTTB for everything. Rejected — min/max binning is O(n) and correct for OHLC extremes; LTTB is a polish option for lines only.
+- **Trade-off**: zoomed-out tooltips report the aggregated candle/bar rather than one specific tick. Accepted (standard practice); per-tick detail returns on zoom-in.
+
+## Risks / Trade-offs
+
+- **[Risk] A scale-canvas or image path misses the zoom-change recreation** → inconsistent crispness across mixed-DPI moves. Mitigation: single `createBackingImage` helper used by all three canvases; verify visually at 200% during implementation.
+- **[Risk] Downsampling alters zoomed-out visuals and tooltips** → expected behavior change. Mitigation: spec already defines one-aggregate-per-column; confirm the min/max OHLC result with a large history in review.
+- **[Risk] Theme defaults shift user-visible colors** for charts that had no explicit colors → intended by design (spec `chart-theme`), but only affects charts with no saved colors.
+- **[Risk] Per-frame allocation regressions** → geometry and aggregation caches reuse objects; keep pooling behavior from the current `pointArray` pattern.
+- **[Risk] SWT version drift** (zoom APIs) → guard with fallback to `getDPI()`; `Monitor.getZoom()` exists in the fixed 2024-03 target.
+
+## Migration Plan
+
+- Pure UI change; no persisted data or API breakage. Chart-object constructors and `MainChartFactory` keep their signatures.
+- Rollback: revert the change — existing layouts and preferences are untouched.
+- Land independently of the extracted PR #18 bug fixes (tooltip + resource disposal), which merge first on master.
diff --git a/openspec/changes/modernize-chart-rendering/proposal.md b/openspec/changes/modernize-chart-rendering/proposal.md
new file mode 100644
index 000000000..972403847
--- /dev/null
+++ b/openspec/changes/modernize-chart-rendering/proposal.md
@@ -0,0 +1,30 @@
+## Why
+
+The chart rendering pipeline dates to the 2004-era Eclipse Trader codebase. It draws to a pixel-sized offscreen image that is recreated only on resize and never re-synced to the display's zoom factor, so output can be stale or blurry when the device zoom changes (mixed-DPI monitors, OS scale changes). It scatters raw `RGB` literals across ~8 classes (no single palette to maintain, no path to dark mode), and rebuilds all point geometry on every repaint (janky on large histories). PR #18 only polished the surface (antialiasing + a one-off palette) without addressing these structural issues, so it has been closed and its durable bug fixes extracted separately.
+
+## What Changes
+
+- **HiDPI / device-zoom-aware rendering**: offscreen chart images are recreated when the display's zoom factor changes, so charts stay crisp across DPI/scale changes (SWT 4.31's image/GC pipeline already renders at native resolution).
+- **Theme / palette service**: a theme provider centralizes all chart colors (line, positive/negative, outline, grid, background). The chart classes and `MainPropertiesPage` source their colors from it instead of inline `new RGB(...)`. Saved user color preferences continue to be honored. Dark mode becomes possible later without rework.
+- **Performance: geometry caching + downsampling**: computed point geometry (candles/bars/points) is cached and invalidated only when data or bounds change; large series are downsampled (min/max binning) so the number of rendered points stays bounded by pixels.
+- **Rendering quality**: antialiasing and text antialiasing enabled for all chart drawing (retained from the closed PR, re-applied on the new pipeline).
+- Supersedes the cosmetic portions of the closed PR #18. Its durable bug fixes (Close-vs-High tooltips, `SummaryOHLCItem` resource disposal) land as a separate small bugfix PR first.
+
+## Capabilities
+
+### New Capabilities
+
+- `chart-rendering`: the chart drawing pipeline — device-zoom-aware output, geometry caching, and dataset downsampling so charts render correctly and smoothly at any display scale and data size.
+- `chart-theme`: the centralized color/theme provider that all chart objects and chart property pages source their colors from, including honoring user-saved color preferences.
+
+### Modified Capabilities
+
+None — this repo has no existing specs yet; both capabilities are new.
+
+## Impact
+
+- **Code**: `org.eclipsetrader.ui` charts package — `Graphics`, `ChartCanvas`, `CandleStickChart`, `BarChart`, `OHLCLineChart`, `HistogramAreaChart`, `HistogramBarChart`, `LineChart`, axis classes — plus `MainChartFactory`, `MainPropertiesPage`, and `org.eclipsetrader.ui.charts.indicators` (`Util`). Chart templates unchanged (they already default to candles).
+- **API**: new `ChartTheme`/`ChartThemes` provider API; chart object constructors keep their signatures and fall back to the shared default theme, so existing callers keep compiling.
+- **Dependencies**: none new — SWT only. Antialiasing and zoom awareness are standard SWT/GC features.
+- **Persistence**: existing saved chart color preferences remain valid and override theme defaults.
+- **Related work**: closed PR #18 is not merged; its extracted bug fixes ship as a separate small PR.
diff --git a/openspec/changes/modernize-chart-rendering/specs/chart-rendering/spec.md b/openspec/changes/modernize-chart-rendering/specs/chart-rendering/spec.md
new file mode 100644
index 000000000..0748de7e6
--- /dev/null
+++ b/openspec/changes/modernize-chart-rendering/specs/chart-rendering/spec.md
@@ -0,0 +1,61 @@
+## Purpose
+
+Defines how chart rendering behaves across display scales and dataset sizes: crisp device-zoom-aware output, cached geometry, and bounded work per repaint on large histories.
+
+## ADDED Requirements
+
+### Requirement: Device-zoom-aware rendering
+
+The chart rendering SHALL recreate the offscreen image when the display's zoom factor (DPI scaling) changes, so chart output stays crisp across display-scale changes.
+
+#### Scenario: High-zoom display
+
+- **WHEN** a chart is displayed on a device with a zoom factor greater than 100% (e.g., 200%)
+- **THEN** candles, bars, lines, grid, and axis text render at the correct scaled size without blur or aliasing artifacts
+
+#### Scenario: Zoom change after image creation
+
+- **WHEN** the display zoom factor changes while a chart is displayed (e.g., the window moves to a monitor with a different scale)
+- **THEN** the offscreen image is recreated at the new zoom and the chart renders crisply
+
+#### Scenario: Standard display unchanged
+
+- **WHEN** a chart is displayed on a device with a zoom factor of 100%
+- **THEN** rendering is equivalent in content and layout to current behavior
+
+### Requirement: Geometry caching
+
+Chart objects SHALL reuse cached point geometry across repaints and SHALL recompute geometry only when the underlying data or visible bounds change.
+
+#### Scenario: Repaint without data change
+
+- **WHEN** a chart repaints (for example on focus change or redraw request) without new data or a bounds change
+- **THEN** the previously computed point geometry is reused and no full geometry rebuild occurs
+
+#### Scenario: Data or bounds change invalidates cache
+
+- **WHEN** new data arrives or the visible date range changes
+- **THEN** the cached geometry is invalidated and recomputed on the next repaint
+
+### Requirement: Downsampling of large series
+
+When the number of data points in the visible range exceeds the available horizontal pixels, the renderer SHALL aggregate points per pixel column so the number of drawn elements stays bounded by the chart width and rendering remains responsive.
+
+#### Scenario: Zoomed-out large history
+
+- **WHEN** a series with more points than horizontal pixels is displayed
+- **THEN** the chart draws at most one aggregate element (candle, bar, or point) per pixel column using a min/max style aggregation
+
+#### Scenario: Small history unchanged
+
+- **WHEN** the visible range contains fewer points than horizontal pixels
+- **THEN** every data point is drawn individually without aggregation
+
+### Requirement: Antialiased drawing
+
+All chart drawing SHALL be performed with shape and text antialiasing enabled.
+
+#### Scenario: Chart rendering quality
+
+- **WHEN** a chart is drawn
+- **THEN** diagonal lines and text edges are antialiased rather than jagged
diff --git a/openspec/changes/modernize-chart-rendering/specs/chart-theme/spec.md b/openspec/changes/modernize-chart-rendering/specs/chart-theme/spec.md
new file mode 100644
index 000000000..98daa8f29
--- /dev/null
+++ b/openspec/changes/modernize-chart-rendering/specs/chart-theme/spec.md
@@ -0,0 +1,42 @@
+## Purpose
+
+Provides a single source of truth for chart colors so the palette is maintainable, dark mode is possible, and user-saved color preferences keep working.
+
+## ADDED Requirements
+
+### Requirement: Centralized chart palette
+
+All chart colors (positive, negative, outline, line, grid, and background) SHALL be sourced from a theme provider rather than inline color literals inside chart objects.
+
+#### Scenario: Default-theme chart
+
+- **WHEN** a chart object is created without explicitly configured colors
+- **THEN** it uses the colors defined by the active theme
+
+#### Scenario: Theme palette change
+
+- **WHEN** the active theme's palette is updated
+- **THEN** charts rendering with theme colors reflect the new colors on their next repaint
+
+### Requirement: User color preferences override theme
+
+User-saved chart color preferences SHALL take precedence over theme defaults for the configured chart.
+
+#### Scenario: Custom candle colors
+
+- **WHEN** a user has saved custom candle colors for a chart
+- **THEN** the chart renders with exactly those colors regardless of the active theme
+
+#### Scenario: Reverting to theme colors
+
+- **WHEN** a chart has no custom colors saved
+- **THEN** it renders with the active theme's colors
+
+### Requirement: Theme covers every renderer
+
+The theme provider SHALL supply colors for all supported chart renderings: candlesticks, bars, OHLC lines, and histograms.
+
+#### Scenario: Style switch keeps theme colors
+
+- **WHEN** a user switches a chart's rendering style (for example from candles to bars)
+- **THEN** the new style uses the theme's colors appropriate to that renderer
diff --git a/openspec/changes/modernize-chart-rendering/tasks.md b/openspec/changes/modernize-chart-rendering/tasks.md
new file mode 100644
index 000000000..7103e9f65
--- /dev/null
+++ b/openspec/changes/modernize-chart-rendering/tasks.md
@@ -0,0 +1,29 @@
+## 1. Theme layer
+
+- [x] 1.1 Add `ChartTheme` value object (line, positive, negative, outline, grid, background RGB) and `ChartThemes.getDefault()` exposing the default Material palette (line `33,150,243`, positive `38,166,154`, negative `239,83,80`, outline `64,64,64`)
+- [x] 1.2 Replace inline `new RGB(...)` field/constructor fallbacks in `CandleStickChart`, `BarChart`, `HistogramBarChart`, `HistogramAreaChart`, `OHLCLineChart`, and `LineChart` with lookups from `ChartThemes.getDefault()`, keeping constructor color parameters and their null-fallback semantics
+- [x] 1.3 Source `MainChartFactory` color field defaults and `MainPropertiesPage` color-selector defaults from the same theme
+- [ ] 1.4 Verify: charts created without explicit colors render with the theme palette, and user-saved colors still override them (spec `chart-theme`)
+
+## 2. HiDPI rendering
+
+- [x] 2.1 Add a `ChartUtils.createBackingImage(Canvas, Rectangle)` helper that creates a logical-size offscreen image via `new Image(display, width, height)` (SWT 4.31 auto-scales it to native pixels), records the creation zoom from the canvas's monitor (`Monitor.getZoom()`, falling back to `display.getDPI().x / 72`), and exposes whether the image needs recreating when the zoom differs
+- [x] 2.2 Migrate the main offscreen image in `ChartCanvas.onPaint` to the helper (dispose and recreate on resize or zoom change, as today)
+- [x] 2.3 Migrate the date scale canvas and vertical scale canvas image creation to the same helper (recreate on zoom change too)
+- [x] 2.4 Enable shape and text antialiasing in the `Graphics` constructor (spec `chart-rendering` — Antialiased drawing)
+- [ ] 2.5 Verify charts render crisply at 100%, 150%, and 200% display zoom (simulate with `GDK_SCALE`) with no blur and no layout regression, and that a zoom change after image creation recreates the offscreen image (spec `chart-rendering` — Device-zoom-aware rendering)
+
+## 3. Downsampling and geometry caching
+
+- [x] 3.1 Add `OHLCDownsampler` with min/max binning per pixel column (preserving high/low, first-open/last-close for candles) returning an aggregated `IOHLC[]`
+- [x] 3.2 Add the scalar equivalent for line/area/histogram renderers
+- [x] 3.3 Wire downsampling into `setDataBounds`: when visible points exceed the chart width, aggregate before geometry build; cache keyed by (firstDate, lastDate, width) and cleared on `invalidate()` (spec `chart-rendering` — Geometry caching)
+- [x] 3.4 Apply to `CandleStickChart`, `BarChart`, `OHLCLineChart`, `HistogramAreaChart`, `HistogramBarChart`, and `LineChart` (spec `chart-rendering` — Downsampling of large series)
+- [ ] 3.5 Verify with a large history: zoomed-out draws at most one element per pixel column, pan/zoom remain responsive, and zoom-in restores full per-tick detail
+
+## 4. Verification
+
+- [ ] 4.1 Add pure-function unit tests for `OHLCDownsampler` (extreme preservation, one-per-column bound, boundary at width == points) and wire `org.eclipsetrader.ui.tests` into the Maven reactor so they run in GitHub Actions CI
+- [ ] 4.2 Build the product with `mvn package` (the existing `.github/workflows/maven.yml` job on JDK 21) and confirm `org.eclipsetrader.ui` and `org.eclipsetrader.ui.charts.indicators` compile
+- [ ] 4.3 Extend the Codespaces devcontainer (`.devcontainer/devcontainer.json`) with a virtual display (e.g., desktop-lite/VNC or Xvfb) and GTK so the SWT product can run headless
+- [ ] 4.4 Visually verify summary bar, crosshair, chart export-to-image, and indicator rendering at 100%/150%/200% zoom (simulate with `GDK_SCALE`) using small and large histories
diff --git a/openspec/config.yaml b/openspec/config.yaml
new file mode 100644
index 000000000..c4d34acea
--- /dev/null
+++ b/openspec/config.yaml
@@ -0,0 +1,32 @@
+schema: spec-driven
+
+# Project context (optional)
+# This is shown to AI when creating artifacts.
+# Add your tech stack, conventions, style guides, domain knowledge, etc.
+# Example:
+# context: |
+# Tech stack: TypeScript, React, Node.js
+# We use conventional commits
+# Domain: e-commerce platform
+
+# Per-artifact rules (optional)
+# Add custom rules for specific artifacts.
+# Example:
+# rules:
+# proposal:
+# - Keep proposals under 500 words
+# - Always include a "Non-goals" section
+# tasks:
+# - Break tasks into chunks of max 2 hours
+
+# Per-operation guidance (optional)
+# Add advisory guidance for how apply and archive work should be conducted.
+# This is separate from artifact rules above.
+# Example:
+# operations:
+# apply:
+# guidance:
+# - Keep test summaries concise
+# archive:
+# guidance:
+# - Summarize the archive outcome before finishing
diff --git a/org.eclipsetrader.core.modern.tests/src/org/eclipsetrader/core/charts/OHLCDownsamplerModernTest.java b/org.eclipsetrader.core.modern.tests/src/org/eclipsetrader/core/charts/OHLCDownsamplerModernTest.java
new file mode 100644
index 000000000..3295531a6
--- /dev/null
+++ b/org.eclipsetrader.core.modern.tests/src/org/eclipsetrader/core/charts/OHLCDownsamplerModernTest.java
@@ -0,0 +1,126 @@
+package org.eclipsetrader.core.charts;
+
+import org.eclipsetrader.core.feed.IOHLC;
+import org.eclipsetrader.core.feed.OHLC;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.platform.runner.JUnitPlatform;
+import org.junit.runner.RunWith;
+
+import java.util.Date;
+
+@RunWith(JUnitPlatform.class)
+public class OHLCDownsamplerModernTest {
+
+ @Test
+ void testNullInput() {
+ IOHLC[] result = OHLCDownsampler.downsample((IOHLC[]) null, 10);
+ Assertions.assertNotNull(result);
+ Assertions.assertEquals(0, result.length);
+ }
+
+ @Test
+ void testEmptyInput() {
+ IOHLC[] result = OHLCDownsampler.downsample(new IOHLC[0], 10);
+ Assertions.assertEquals(0, result.length);
+ }
+
+ @Test
+ void testWidthZeroOrNegativeReturnsSource() {
+ IOHLC[] source = new IOHLC[] { ohlc(0, 10.0, 11.0, 9.0, 10.5) };
+ Assertions.assertSame(source, OHLCDownsampler.downsample(source, 0));
+ Assertions.assertSame(source, OHLCDownsampler.downsample(source, -1));
+ }
+
+ @Test
+ void testWidthExceedsValuesReturnsSource() {
+ IOHLC[] source = new IOHLC[] {
+ ohlc(0, 10.0, 11.0, 9.0, 10.5),
+ ohlc(1, 11.0, 12.0, 10.0, 11.5),
+ };
+ IOHLC[] result = OHLCDownsampler.downsample(source, 3);
+ Assertions.assertSame(source, result);
+ }
+
+ @Test
+ void testWidthEqualsValuesReturnsSource() {
+ IOHLC[] source = new IOHLC[] {
+ ohlc(0, 10.0, 11.0, 9.0, 10.5),
+ ohlc(1, 11.0, 12.0, 10.0, 11.5),
+ };
+ IOHLC[] result = OHLCDownsampler.downsample(source, 2);
+ Assertions.assertSame(source, result);
+ }
+
+ @Test
+ void testBoundaryOnePerColumn() {
+ IOHLC[] source = new IOHLC[100];
+ for (int i = 0; i < 100; i++) {
+ source[i] = ohlc(i, 10.0 + i, 20.0 + i, 5.0 + i, 15.0 + i);
+ }
+ IOHLC[] result = OHLCDownsampler.downsample(source, 7);
+ Assertions.assertEquals(7, result.length);
+ }
+
+ @Test
+ void testPreservesExtremes() {
+ Date d1 = new Date(0);
+ Date d2 = new Date(1);
+ Date d3 = new Date(2);
+ Date d4 = new Date(3);
+
+ IOHLC[] source = new IOHLC[] {
+ new OHLC(d1, 10.0, 15.0, 9.0, 12.0, null),
+ new OHLC(d2, 12.0, 20.0, 8.0, 14.0, null),
+ new OHLC(d3, 14.0, 18.0, 10.0, 13.0, null),
+ new OHLC(d4, 13.0, 16.0, 11.0, 15.0, null),
+ };
+ IOHLC[] result = OHLCDownsampler.downsample(source, 2);
+
+ Assertions.assertEquals(2, result.length);
+
+ Assertions.assertEquals(d1, result[0].getDate());
+ Assertions.assertEquals(10.0, result[0].getOpen(), 0.001);
+ Assertions.assertEquals(20.0, result[0].getHigh(), 0.001);
+ Assertions.assertEquals(8.0, result[0].getLow(), 0.001);
+ Assertions.assertEquals(14.0, result[0].getClose(), 0.001);
+
+ Assertions.assertEquals(d3, result[1].getDate());
+ Assertions.assertEquals(14.0, result[1].getOpen(), 0.001);
+ Assertions.assertEquals(18.0, result[1].getHigh(), 0.001);
+ Assertions.assertEquals(10.0, result[1].getLow(), 0.001);
+ Assertions.assertEquals(15.0, result[1].getClose(), 0.001);
+ }
+
+ @Test
+ void testSingleValuePerBin() {
+ IOHLC[] source = new IOHLC[] {
+ ohlc(0, 10.0, 10.5, 9.5, 10.2),
+ ohlc(1, 11.0, 11.5, 10.5, 11.2),
+ ohlc(2, 12.0, 12.5, 11.5, 12.2),
+ };
+ IOHLC[] result = OHLCDownsampler.downsample(source, 3);
+ Assertions.assertEquals(3, result.length);
+ Assertions.assertEquals(10.0, result[0].getOpen(), 0.001);
+ Assertions.assertEquals(12.2, result[2].getClose(), 0.001);
+ }
+
+ @Test
+ void testOddBinningLastColumnSmaller() {
+ IOHLC[] source = new IOHLC[5];
+ for (int i = 0; i < 5; i++) {
+ source[i] = ohlc(i, 10.0 + i, 15.0 + i, 9.0 + i, 12.0 + i);
+ }
+ IOHLC[] result = OHLCDownsampler.downsample(source, 2);
+ Assertions.assertEquals(2, result.length);
+ Assertions.assertEquals(10.0, result[0].getOpen(), 0.001);
+ }
+
+ private static IOHLC ohlc(long time, double open, double high, double low, double close) {
+ return new OHLC(new Date(time), open, high, low, close, null);
+ }
+
+ private static IOHLC ohlc(int time, double open, double high, double low, double close) {
+ return ohlc((long) time, open, high, low, close);
+ }
+}
diff --git a/org.eclipsetrader.core.modern.tests/src/org/eclipsetrader/core/charts/ScalarDownsamplerModernTest.java b/org.eclipsetrader.core.modern.tests/src/org/eclipsetrader/core/charts/ScalarDownsamplerModernTest.java
new file mode 100644
index 000000000..43e09b6f7
--- /dev/null
+++ b/org.eclipsetrader.core.modern.tests/src/org/eclipsetrader/core/charts/ScalarDownsamplerModernTest.java
@@ -0,0 +1,117 @@
+package org.eclipsetrader.core.charts;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipsetrader.core.charts.NumberValue;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.platform.runner.JUnitPlatform;
+import org.junit.runner.RunWith;
+
+import java.util.Date;
+
+@RunWith(JUnitPlatform.class)
+public class ScalarDownsamplerModernTest {
+
+ @Test
+ void testNullInput() {
+ IAdaptable[] result = ScalarDownsampler.downsample(null, 10);
+ Assertions.assertNotNull(result);
+ Assertions.assertEquals(0, result.length);
+ }
+
+ @Test
+ void testEmptyInput() {
+ IAdaptable[] result = ScalarDownsampler.downsample(new IAdaptable[0], 10);
+ Assertions.assertEquals(0, result.length);
+ }
+
+ @Test
+ void testWidthZeroOrNegativeReturnsSource() {
+ IAdaptable[] source = new IAdaptable[] { nv(0, 10.0) };
+ Assertions.assertSame(source, ScalarDownsampler.downsample(source, 0));
+ Assertions.assertSame(source, ScalarDownsampler.downsample(source, -1));
+ }
+
+ @Test
+ void testWidthExceedsValuesReturnsSource() {
+ IAdaptable[] source = new IAdaptable[] { nv(0, 10.0), nv(1, 20.0) };
+ IAdaptable[] result = ScalarDownsampler.downsample(source, 5);
+ Assertions.assertSame(source, result);
+ }
+
+ @Test
+ void testWidthEqualsValuesReturnsSource() {
+ IAdaptable[] source = new IAdaptable[] { nv(0, 10.0), nv(1, 20.0) };
+ IAdaptable[] result = ScalarDownsampler.downsample(source, 2);
+ Assertions.assertSame(source, result);
+ }
+
+ @Test
+ void testBoundaryOnePerColumn() {
+ IAdaptable[] source = new IAdaptable[100];
+ for (int i = 0; i < 100; i++) {
+ source[i] = nv(i, (double) i);
+ }
+ IAdaptable[] result = ScalarDownsampler.downsample(source, 13);
+ Assertions.assertEquals(13, result.length);
+ }
+
+ @Test
+ void testPicksMaxDeviationPoint() {
+ Date d1 = new Date(0);
+ Date d2 = new Date(1);
+ Date d3 = new Date(2);
+ Date d4 = new Date(3);
+
+ IAdaptable[] source = new IAdaptable[] {
+ new NumberValue(d1, 10.0),
+ new NumberValue(d2, 11.0),
+ new NumberValue(d3, 100.0),
+ new NumberValue(d4, 12.0),
+ };
+ IAdaptable[] result = ScalarDownsampler.downsample(source, 2);
+
+ Assertions.assertEquals(2, result.length);
+ Assertions.assertEquals(10.0, ((Number) result[0].getAdapter(Number.class)).doubleValue(), 0.001);
+ Assertions.assertEquals(100.0, ((Number) result[1].getAdapter(Number.class)).doubleValue(), 0.001);
+ }
+
+ @Test
+ void testPreservesDateAndNumberAdapter() {
+ Date date = new Date(42);
+ IAdaptable[] source = new IAdaptable[] {
+ new NumberValue(date, 10.0),
+ new NumberValue(new Date(43), 11.0),
+ new NumberValue(new Date(44), 12.0),
+ new NumberValue(new Date(45), 13.0),
+ new NumberValue(new Date(46), 14.0),
+ };
+ IAdaptable[] result = ScalarDownsampler.downsample(source, 2);
+
+ Assertions.assertEquals(2, result.length);
+ for (int i = 0; i < result.length; i++) {
+ Assertions.assertNotNull(result[i].getAdapter(Date.class), "column " + i + " should adapt to Date");
+ Assertions.assertNotNull(result[i].getAdapter(Number.class), "column " + i + " should adapt to Number");
+ }
+ }
+
+ @Test
+ void testEqualDeviationPicksFirst() {
+ IAdaptable[] source = new IAdaptable[] {
+ new NumberValue(new Date(0), 0.0),
+ new NumberValue(new Date(1), 100.0),
+ new NumberValue(new Date(2), 0.0),
+ };
+ IAdaptable[] result = ScalarDownsampler.downsample(source, 1);
+ Assertions.assertEquals(1, result.length);
+ Assertions.assertEquals(100.0, ((Number) result[0].getAdapter(Number.class)).doubleValue(), 0.001);
+ }
+
+ private static IAdaptable nv(long time, double value) {
+ return new NumberValue(new Date(time), value);
+ }
+
+ private static IAdaptable nv(int time, double value) {
+ return nv((long) time, value);
+ }
+}
diff --git a/org.eclipsetrader.core/plugin.xml b/org.eclipsetrader.core/plugin.xml
index 67a5c07ba..603132e7f 100644
--- a/org.eclipsetrader.core/plugin.xml
+++ b/org.eclipsetrader.core/plugin.xml
@@ -45,6 +45,13 @@
id="org.eclipsetrader.core.internal.repositories.DefaultElementFactory"
name="Default Element Factory">
-
+
+
+
+
+
+
diff --git a/org.eclipsetrader.core/src/org/eclipsetrader/core/charts/OHLCDownsampler.java b/org.eclipsetrader.core/src/org/eclipsetrader/core/charts/OHLCDownsampler.java
new file mode 100644
index 000000000..cab30a6fa
--- /dev/null
+++ b/org.eclipsetrader.core/src/org/eclipsetrader/core/charts/OHLCDownsampler.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2004-2011 Marco Maccaferri and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marco Maccaferri - initial API and implementation
+ */
+
+package org.eclipsetrader.core.charts;
+
+import java.util.Date;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipsetrader.core.feed.IOHLC;
+import org.eclipsetrader.core.feed.OHLC;
+
+/**
+ * Aggregates OHLC values into one bin per pixel column so that zoomed-out
+ * charts render at most one element per column while preserving each column's
+ * high, low, first open and last close.
+ */
+public final class OHLCDownsampler {
+
+ private OHLCDownsampler() {
+ }
+
+ /**
+ * Aggregates the given values into at most width bins.
+ *
+ * @param values the source values.
+ * @param width the maximum number of bins (pixel columns).
+ * @return the aggregated values, or the source array if no aggregation is needed.
+ */
+ public static IOHLC[] downsample(IOHLC[] values, int width) {
+ if (values == null || values.length == 0) {
+ return new IOHLC[0];
+ }
+ if (width <= 0 || values.length <= width) {
+ return values;
+ }
+
+ IOHLC[] result = new IOHLC[width];
+ for (int column = 0; column < width; column++) {
+ int start = (int) ((long) column * values.length / width);
+ int end = (int) ((long) (column + 1) * values.length / width);
+ if (end <= start) {
+ end = start + 1;
+ }
+ if (end > values.length) {
+ end = values.length;
+ }
+
+ IOHLC first = values[start];
+ IOHLC last = values[end - 1];
+ Double high = first.getHigh();
+ Double low = first.getLow();
+ for (int i = start + 1; i < end; i++) {
+ if (values[i].getHigh() != null && (high == null || values[i].getHigh() > high)) {
+ high = values[i].getHigh();
+ }
+ if (values[i].getLow() != null && (low == null || values[i].getLow() < low)) {
+ low = values[i].getLow();
+ }
+ }
+ result[column] = new OHLC(first.getDate(), first.getOpen(), high, low, last.getClose(), null);
+ }
+ return result;
+ }
+
+ /**
+ * Aggregates the given adaptable OHLC values into at most width
+ * bins, returning values that adapt to IOHLC, Date
+ * and Number.
+ *
+ * @param values the source values.
+ * @param width the maximum number of bins (pixel columns).
+ * @return the aggregated values, or the source array if no aggregation is needed.
+ */
+ public static IAdaptable[] downsample(IAdaptable[] values, int width) {
+ if (values == null || values.length == 0) {
+ return new IAdaptable[0];
+ }
+ if (width <= 0 || values.length <= width) {
+ return values;
+ }
+
+ IOHLC[] source = new IOHLC[values.length];
+ int count = 0;
+ for (int i = 0; i < values.length; i++) {
+ IOHLC ohlc = (IOHLC) values[i].getAdapter(IOHLC.class);
+ if (ohlc != null) {
+ source[count++] = ohlc;
+ }
+ }
+ if (count == 0) {
+ return new IAdaptable[0];
+ }
+ if (count < source.length) {
+ IOHLC[] compact = new IOHLC[count];
+ System.arraycopy(source, 0, compact, 0, count);
+ source = compact;
+ }
+
+ IOHLC[] aggregated = downsample(source, width);
+
+ IAdaptable[] result = new IAdaptable[aggregated.length];
+ for (int i = 0; i < aggregated.length; i++) {
+ result[i] = new Value(aggregated[i]);
+ }
+ return result;
+ }
+
+ private static class Value implements IAdaptable {
+
+ private final IOHLC ohlc;
+
+ public Value(IOHLC ohlc) {
+ this.ohlc = ohlc;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
+ */
+ @Override
+ @SuppressWarnings({
+ "unchecked", "rawtypes"
+ })
+ public Object getAdapter(Class adapter) {
+ if (ohlc != null && adapter.isAssignableFrom(ohlc.getClass())) {
+ return ohlc;
+ }
+ if (adapter.isAssignableFrom(Date.class)) {
+ return ohlc != null ? ohlc.getDate() : null;
+ }
+ if (adapter.isAssignableFrom(Double.class) || adapter.isAssignableFrom(Number.class)) {
+ return ohlc != null ? ohlc.getClose() : null;
+ }
+ return null;
+ }
+ }
+}
diff --git a/org.eclipsetrader.core/src/org/eclipsetrader/core/charts/ScalarDownsampler.java b/org.eclipsetrader.core/src/org/eclipsetrader/core/charts/ScalarDownsampler.java
new file mode 100644
index 000000000..596f34b98
--- /dev/null
+++ b/org.eclipsetrader.core/src/org/eclipsetrader/core/charts/ScalarDownsampler.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2004-2011 Marco Maccaferri and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marco Maccaferri - initial API and implementation
+ */
+
+package org.eclipsetrader.core.charts;
+
+import java.util.Date;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipsetrader.core.charts.NumberValue;
+
+/**
+ * Aggregates scalar (date, number) values into one bin per pixel column so that
+ * zoomed-out charts render at most one element per column. Each bin keeps the
+ * value with the greatest deviation from the column mean, which preserves
+ * spikes in the rendered line, area or histogram.
+ */
+public final class ScalarDownsampler {
+
+ private ScalarDownsampler() {
+ }
+
+ /**
+ * Aggregates the given values into at most width bins.
+ *
+ * @param values the source values, adapting to Date and Number.
+ * @param width the maximum number of bins (pixel columns).
+ * @return the aggregated values, or the source array if no aggregation is needed.
+ */
+ public static IAdaptable[] downsample(IAdaptable[] values, int width) {
+ if (values == null || values.length == 0) {
+ return new IAdaptable[0];
+ }
+ if (width <= 0 || values.length <= width) {
+ return values;
+ }
+
+ IAdaptable[] result = new IAdaptable[width];
+ for (int column = 0; column < width; column++) {
+ int start = (int) ((long) column * values.length / width);
+ int end = (int) ((long) (column + 1) * values.length / width);
+ if (end <= start) {
+ end = start + 1;
+ }
+ if (end > values.length) {
+ end = values.length;
+ }
+
+ double sum = 0.0;
+ int count = 0;
+ for (int i = start; i < end; i++) {
+ Number number = (Number) values[i].getAdapter(Number.class);
+ if (number != null) {
+ sum += number.doubleValue();
+ count++;
+ }
+ }
+ double mean = count != 0 ? sum / count : 0.0;
+
+ int representative = -1;
+ double bestDeviation = -1.0;
+ for (int i = start; i < end; i++) {
+ Number number = (Number) values[i].getAdapter(Number.class);
+ if (number == null) {
+ continue;
+ }
+ double deviation = Math.abs(number.doubleValue() - mean);
+ if (representative == -1 || deviation > bestDeviation) {
+ representative = i;
+ bestDeviation = deviation;
+ }
+ }
+ if (representative == -1) {
+ representative = end - 1;
+ }
+
+ IAdaptable value = values[representative];
+ Date date = (Date) value.getAdapter(Date.class);
+ Number number = (Number) value.getAdapter(Number.class);
+ result[column] = new NumberValue(date, number);
+ }
+ return result;
+ }
+}
diff --git a/org.eclipsetrader.core/src/org/eclipsetrader/core/internal/PreferenceInitializer.java b/org.eclipsetrader.core/src/org/eclipsetrader/core/internal/PreferenceInitializer.java
new file mode 100644
index 000000000..6fd6e70db
--- /dev/null
+++ b/org.eclipsetrader.core/src/org/eclipsetrader/core/internal/PreferenceInitializer.java
@@ -0,0 +1,17 @@
+package org.eclipsetrader.core.internal;
+
+import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
+import org.eclipse.core.runtime.preferences.DefaultScope;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipsetrader.core.internal.CoreActivator;
+
+public class PreferenceInitializer extends AbstractPreferenceInitializer {
+
+ private static final String JESSX_STREAMING_CONNECTOR = "org.eclipsetrader.jessx.connector";
+
+ @Override
+ public void initializeDefaultPreferences() {
+ IEclipsePreferences node = DefaultScope.INSTANCE.getNode(CoreActivator.PLUGIN_ID);
+ node.put(CoreActivator.DEFAULT_CONNECTOR_ID, JESSX_STREAMING_CONNECTOR);
+ }
+}
diff --git a/org.eclipsetrader.platform/plugin.xml b/org.eclipsetrader.platform/plugin.xml
index a05dfc0d9..1747e71a9 100644
--- a/org.eclipsetrader.platform/plugin.xml
+++ b/org.eclipsetrader.platform/plugin.xml
@@ -21,6 +21,9 @@
+
diff --git a/org.eclipsetrader.platform/plugin_customization.ini b/org.eclipsetrader.platform/plugin_customization.ini
index 0966cf0b6..08b5b5398 100644
--- a/org.eclipsetrader.platform/plugin_customization.ini
+++ b/org.eclipsetrader.platform/plugin_customization.ini
@@ -29,8 +29,8 @@ org.eclipse.update.core/org.eclipse.update.core.updateVersions=compatible
org.eclipsetrader.ui/EXIT_PROMPT_ON_CLOSE_LAST_WINDOW=true
# default connectors
-org.eclipsetrader.core/DEFAULT_CONNECTOR=org.eclipsetrader.yahoo
-org.eclipsetrader.core/DEFAULT_BACKFILL_CONNECTOR=org.eclipsetrader.yahoo
+org.eclipsetrader.core/DEFAULT_CONNECTOR=org.eclipsetrader.jessx.connector
+org.eclipsetrader.core/DEFAULT_BACKFILL_CONNECTOR=org.eclipsetrader.jessx.connector
# chart defaults
org.eclipsetrader.ui/INITIAL_BACKFILL_METHOD=0
diff --git a/org.eclipsetrader.ui/data/basic-template.xml b/org.eclipsetrader.ui/data/basic-template.xml
index b6fa15c94..711b23cb4 100644
--- a/org.eclipsetrader.ui/data/basic-template.xml
+++ b/org.eclipsetrader.ui/data/basic-template.xml
@@ -3,7 +3,7 @@
Basic
-
+
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/BarChart.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/BarChart.java
index 9af4fbd28..2f5039194 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/BarChart.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/BarChart.java
@@ -23,6 +23,7 @@
import org.eclipse.swt.widgets.Composite;
import org.eclipsetrader.core.charts.IDataSeries;
import org.eclipsetrader.core.charts.OHLCDataSeries;
+import org.eclipsetrader.core.charts.OHLCDownsampler;
import org.eclipsetrader.core.feed.IOHLC;
import org.eclipsetrader.core.feed.TimeSpan;
@@ -31,10 +32,13 @@ public class BarChart implements IChartObject, ISummaryBarDecorator, IAdaptable
private IDataSeries dataSeries;
private int width = 5;
- private RGB positiveColor = new RGB(0, 254, 0);
- private RGB negativeColor = new RGB(254, 0, 0);
+ private RGB positiveColor = ChartThemes.getDefault().getPositive();
+ private RGB negativeColor = ChartThemes.getDefault().getNegative();
private IAdaptable[] values;
+ private Date firstDate;
+ private Date lastDate;
+ private int pixelWidth;
private List pointArray;
private boolean valid;
private boolean hasFocus;
@@ -89,6 +93,11 @@ public BarChart(IDataSeries dataSeries, RGB positiveColor, RGB negativeColor) {
*/
@Override
public void setDataBounds(DataBounds dataBounds) {
+ this.width = dataBounds.horizontalSpacing;
+ if (isSameRange(dataBounds)) {
+ return;
+ }
+
List l = new ArrayList(2048);
for (IAdaptable value : dataSeries.getValues()) {
Date date = (Date) value.getAdapter(Date.class);
@@ -96,17 +105,36 @@ public void setDataBounds(DataBounds dataBounds) {
l.add(value);
}
}
- this.values = l.toArray(new IAdaptable[l.size()]);
- this.width = dataBounds.horizontalSpacing;
+ IAdaptable[] visible = l.toArray(new IAdaptable[l.size()]);
+ this.values = OHLCDownsampler.downsample(visible, dataBounds.width);
+ this.firstDate = dataBounds.first;
+ this.lastDate = dataBounds.last;
+ this.pixelWidth = dataBounds.width;
this.valid = false;
}
+ private boolean isSameRange(DataBounds dataBounds) {
+ if (values == null || pixelWidth != dataBounds.width) {
+ return false;
+ }
+ if (firstDate != dataBounds.first && (firstDate == null || !firstDate.equals(dataBounds.first))) {
+ return false;
+ }
+ if (lastDate != dataBounds.last && (lastDate == null || !lastDate.equals(dataBounds.last))) {
+ return false;
+ }
+ return true;
+ }
+
/* (non-Javadoc)
* @see org.eclipsetrader.ui.charts.IChartObject#invalidate()
*/
@Override
public void invalidate() {
this.valid = false;
+ this.values = null;
+ this.firstDate = null;
+ this.lastDate = null;
}
/* (non-Javadoc)
@@ -333,10 +361,11 @@ public void setColor(RGB color) {
}
public void paint(IGraphics graphics) {
+ int barWidth = Math.max(width, 3);
graphics.setForegroundColor(color);
graphics.drawLine(x, yHigh, x, yLow);
- graphics.drawLine(x - width / 2, yOpen, x, yOpen);
- graphics.drawLine(x, yClose, x + width / 2, yClose);
+ graphics.drawLine(x - barWidth / 2, yOpen, x, yOpen);
+ graphics.drawLine(x, yClose, x + barWidth / 2, yClose);
}
public boolean containsPoint(int x, int y) {
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/CandleStickChart.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/CandleStickChart.java
index e820cb7ad..732f2058f 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/CandleStickChart.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/CandleStickChart.java
@@ -22,6 +22,7 @@
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipsetrader.core.charts.IDataSeries;
+import org.eclipsetrader.core.charts.OHLCDownsampler;
import org.eclipsetrader.core.feed.IOHLC;
/**
@@ -34,11 +35,14 @@ public class CandleStickChart implements IChartObject, ISummaryBarDecorator, IAd
private IDataSeries dataSeries;
private int width = 5;
- private RGB outlineColor = new RGB(0, 0, 0);
- private RGB positiveColor = new RGB(254, 254, 254);
- private RGB negativeColor = new RGB(0, 0, 0);
+ private RGB outlineColor = ChartThemes.getDefault().getOutline();
+ private RGB positiveColor = ChartThemes.getDefault().getPositive();
+ private RGB negativeColor = ChartThemes.getDefault().getNegative();
private IAdaptable[] values;
+ private Date firstDate;
+ private Date lastDate;
+ private int pixelWidth;
private List pointArray;
private boolean valid;
private boolean hasFocus;
@@ -81,6 +85,11 @@ public CandleStickChart(IDataSeries dataSeries, RGB outlineColor, RGB positiveCo
*/
@Override
public void setDataBounds(DataBounds dataBounds) {
+ this.width = dataBounds.horizontalSpacing;
+ if (isSameRange(dataBounds)) {
+ return;
+ }
+
List l = new ArrayList(2048);
for (IAdaptable value : dataSeries.getValues()) {
Date date = (Date) value.getAdapter(Date.class);
@@ -88,17 +97,36 @@ public void setDataBounds(DataBounds dataBounds) {
l.add(value);
}
}
- this.values = l.toArray(new IAdaptable[l.size()]);
- this.width = dataBounds.horizontalSpacing;
+ IAdaptable[] visible = l.toArray(new IAdaptable[l.size()]);
+ this.values = OHLCDownsampler.downsample(visible, dataBounds.width);
+ this.firstDate = dataBounds.first;
+ this.lastDate = dataBounds.last;
+ this.pixelWidth = dataBounds.width;
this.valid = false;
}
+ private boolean isSameRange(DataBounds dataBounds) {
+ if (values == null || pixelWidth != dataBounds.width) {
+ return false;
+ }
+ if (firstDate != dataBounds.first && (firstDate == null || !firstDate.equals(dataBounds.first))) {
+ return false;
+ }
+ if (lastDate != dataBounds.last && (lastDate == null || !lastDate.equals(dataBounds.last))) {
+ return false;
+ }
+ return true;
+ }
+
/* (non-Javadoc)
* @see org.eclipsetrader.ui.charts.IChartObject#invalidate()
*/
@Override
public void invalidate() {
this.valid = false;
+ this.values = null;
+ this.firstDate = null;
+ this.lastDate = null;
}
/* (non-Javadoc)
@@ -326,17 +354,20 @@ public void setOhlc(IOHLC ohlc) {
}
public void paint(IGraphics graphics) {
+ int bodyWidth = Math.max(width, 2);
graphics.setForegroundColor(outlineColor);
graphics.drawLine(x, yHigh, x, yLow);
if (yOpen < yClose) {
+ int bodyHeight = Math.max(yClose - yOpen, 1);
graphics.setBackgroundColor(fillColor);
- graphics.fillRectangle(x - width / 2, yOpen, width, yClose - yOpen);
- graphics.drawRectangle(x - width / 2, yOpen, width - 1, yClose - yOpen - 1);
+ graphics.fillRectangle(x - bodyWidth / 2, yOpen, bodyWidth, bodyHeight);
+ graphics.drawRectangle(x - bodyWidth / 2, yOpen, bodyWidth - 1, bodyHeight - 1);
}
else {
+ int bodyHeight = Math.max(yOpen - yClose, 1);
graphics.setBackgroundColor(fillColor);
- graphics.fillRectangle(x - width / 2, yClose, width, yOpen - yClose);
- graphics.drawRectangle(x - width / 2, yClose, width - 1, yOpen - yClose - 1);
+ graphics.fillRectangle(x - bodyWidth / 2, yClose, bodyWidth, bodyHeight);
+ graphics.drawRectangle(x - bodyWidth / 2, yClose, bodyWidth - 1, bodyHeight - 1);
}
}
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/ChartCanvas.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/ChartCanvas.java
index 54bff9aa6..ccefc26d3 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/ChartCanvas.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/ChartCanvas.java
@@ -53,6 +53,8 @@ public class ChartCanvas {
private Image image;
private Image verticalScaleImage;
+ private int imageZoom;
+ private int verticalScaleImageZoom;
private Label label;
@@ -250,8 +252,13 @@ private void onPaint(PaintEvent event) {
image.dispose();
}
}
- if (image == null || image.isDisposed()) {
- image = new Image(canvas.getDisplay(), clientArea.width, clientArea.height);
+ int zoom = ChartUtils.getZoom(canvas);
+ if (image == null || image.isDisposed() || imageZoom != zoom) {
+ if (image != null && !image.isDisposed()) {
+ image.dispose();
+ }
+ image = ChartUtils.createBackingImage(canvas, clientArea);
+ imageZoom = zoom;
needsRedraw = true;
}
@@ -360,8 +367,13 @@ private void onPaintVerticalScale(PaintEvent event) {
verticalScaleImage.dispose();
}
}
- if (verticalScaleImage == null || verticalScaleImage.isDisposed()) {
- verticalScaleImage = new Image(verticalScaleCanvas.getDisplay(), clientArea.width, clientArea.height);
+ int zoom = ChartUtils.getZoom(verticalScaleCanvas);
+ if (verticalScaleImage == null || verticalScaleImage.isDisposed() || verticalScaleImageZoom != zoom) {
+ if (verticalScaleImage != null && !verticalScaleImage.isDisposed()) {
+ verticalScaleImage.dispose();
+ }
+ verticalScaleImage = ChartUtils.createBackingImage(verticalScaleCanvas, clientArea);
+ verticalScaleImageZoom = zoom;
needsRedraw = true;
}
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/ChartTheme.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/ChartTheme.java
new file mode 100644
index 000000000..8cae1c51a
--- /dev/null
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/ChartTheme.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2004-2011 Marco Maccaferri and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marco Maccaferri - initial API and implementation
+ */
+
+package org.eclipsetrader.ui.charts;
+
+import org.eclipse.swt.graphics.RGB;
+
+/**
+ * Immutable chart color theme.
+ *
+ * @since 1.0
+ */
+public class ChartTheme {
+
+ private final RGB line;
+ private final RGB positive;
+ private final RGB negative;
+ private final RGB outline;
+ private final RGB grid;
+ private final RGB background;
+
+ public ChartTheme(RGB line, RGB positive, RGB negative, RGB outline, RGB grid, RGB background) {
+ this.line = line;
+ this.positive = positive;
+ this.negative = negative;
+ this.outline = outline;
+ this.grid = grid;
+ this.background = background;
+ }
+
+ public RGB getLine() {
+ return line;
+ }
+
+ public RGB getPositive() {
+ return positive;
+ }
+
+ public RGB getNegative() {
+ return negative;
+ }
+
+ public RGB getOutline() {
+ return outline;
+ }
+
+ public RGB getGrid() {
+ return grid;
+ }
+
+ public RGB getBackground() {
+ return background;
+ }
+}
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/ChartThemes.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/ChartThemes.java
new file mode 100644
index 000000000..c228a7e98
--- /dev/null
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/ChartThemes.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2004-2011 Marco Maccaferri and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marco Maccaferri - initial API and implementation
+ */
+
+package org.eclipsetrader.ui.charts;
+
+import org.eclipse.swt.graphics.RGB;
+
+/**
+ * Holds the available chart themes.
+ *
+ * @since 1.0
+ */
+public final class ChartThemes {
+
+ private static final ChartTheme DEFAULT = new ChartTheme(
+ new RGB(33, 150, 243), new RGB(38, 166, 154), new RGB(239, 83, 80), new RGB(64, 64, 64), new RGB(224, 224, 224), new RGB(255, 255, 255));
+
+ private ChartThemes() {
+ }
+
+ public static ChartTheme getDefault() {
+ return DEFAULT;
+ }
+}
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/ChartUtils.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/ChartUtils.java
new file mode 100644
index 000000000..fc2e166a2
--- /dev/null
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/ChartUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2004-2011 Marco Maccaferri and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marco Maccaferri - initial API and implementation
+ */
+
+package org.eclipsetrader.ui.charts;
+
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+
+/**
+ * Chart rendering utilities.
+ *
+ * @since 1.0
+ */
+public final class ChartUtils {
+
+ private ChartUtils() {
+ }
+
+ /**
+ * Creates the offscreen image backing a chart canvas. The image is sized in
+ * logical (device-independent) pixels; SWT 4.31 auto-scales it to the
+ * device's native resolution at creation time.
+ *
+ * @param canvas the canvas the image backs
+ * @param bounds the logical size of the image
+ * @return the offscreen image
+ */
+ public static Image createBackingImage(Canvas canvas, Rectangle bounds) {
+ return new Image(canvas.getDisplay(), bounds.width, bounds.height);
+ }
+
+ /**
+ * Returns the display zoom (percent) of the canvas's monitor, falling back
+ * to the DPI-derived zoom when the monitor zoom is unavailable.
+ *
+ * @param canvas the canvas
+ * @return the zoom factor in percent (100 = no scaling)
+ */
+ public static int getZoom(Canvas canvas) {
+ int zoom = canvas.getMonitor().getZoom();
+ if (zoom <= 0) {
+ zoom = canvas.getDisplay().getDPI().x * 100 / 72;
+ }
+ return zoom;
+ }
+}
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/DateScaleCanvas.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/DateScaleCanvas.java
index 3cd98277a..3883138cf 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/DateScaleCanvas.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/DateScaleCanvas.java
@@ -45,6 +45,7 @@ public class DateScaleCanvas {
private Canvas horizontalScaleCanvas;
private Image horizontalScaleImage;
+ private int horizontalScaleImageZoom;
private Label label;
private TimeSpan resolutionTimeSpan;
@@ -136,8 +137,13 @@ private void onPaint(PaintEvent event) {
horizontalScaleImage.dispose();
}
}
- if (horizontalScaleImage == null || horizontalScaleImage.isDisposed()) {
- horizontalScaleImage = new Image(horizontalScaleCanvas.getDisplay(), clientArea.width, clientArea.height);
+ int zoom = ChartUtils.getZoom(horizontalScaleCanvas);
+ if (horizontalScaleImage == null || horizontalScaleImage.isDisposed() || horizontalScaleImageZoom != zoom) {
+ if (horizontalScaleImage != null && !horizontalScaleImage.isDisposed()) {
+ horizontalScaleImage.dispose();
+ }
+ horizontalScaleImage = ChartUtils.createBackingImage(horizontalScaleCanvas, clientArea);
+ horizontalScaleImageZoom = zoom;
needsRedraw = true;
}
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/Graphics.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/Graphics.java
index 3f2a1f193..f971a2acb 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/Graphics.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/Graphics.java
@@ -43,6 +43,8 @@ public class Graphics implements IGraphics {
public Graphics(Drawable drawable, Point location, IAxis horizontalAxis, IAxis verticalAxis) {
this.gc = new GC(drawable);
+ this.gc.setAntialias(SWT.ON);
+ this.gc.setTextAntialias(SWT.ON);
this.horizontalAxis = horizontalAxis;
this.verticalAxis = verticalAxis;
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/HistogramAreaChart.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/HistogramAreaChart.java
index 249605683..af7f19f7d 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/HistogramAreaChart.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/HistogramAreaChart.java
@@ -23,6 +23,7 @@
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipsetrader.core.charts.IDataSeries;
+import org.eclipsetrader.core.charts.ScalarDownsampler;
import org.eclipsetrader.core.feed.IOHLC;
/**
@@ -36,11 +37,14 @@ public class HistogramAreaChart implements IChartObject, ISummaryBarDecorator, I
private OHLCField field;
private IAdaptable[] values;
+ private Date firstDate;
+ private Date lastDate;
+ private int pixelWidth;
private List pointArray = new ArrayList(2048);
private boolean valid;
private boolean focus;
- private RGB color = new RGB(0, 0, 0);
+ private RGB color = ChartThemes.getDefault().getLine();
private RGB fillColor;
private SummaryDateItem dateItem;
@@ -81,6 +85,10 @@ public void setColor(RGB color) {
*/
@Override
public void setDataBounds(DataBounds dataBounds) {
+ if (isSameRange(dataBounds)) {
+ return;
+ }
+
List l = new ArrayList(2048);
for (IAdaptable value : dataSeries.getValues()) {
Date date = (Date) value.getAdapter(Date.class);
@@ -88,16 +96,36 @@ public void setDataBounds(DataBounds dataBounds) {
l.add(value);
}
}
- this.values = l.toArray(new IAdaptable[l.size()]);
+ IAdaptable[] visible = l.toArray(new IAdaptable[l.size()]);
+ this.values = ScalarDownsampler.downsample(visible, dataBounds.width);
+ this.firstDate = dataBounds.first;
+ this.lastDate = dataBounds.last;
+ this.pixelWidth = dataBounds.width;
this.valid = false;
}
+ private boolean isSameRange(DataBounds dataBounds) {
+ if (values == null || pixelWidth != dataBounds.width) {
+ return false;
+ }
+ if (firstDate != dataBounds.first && (firstDate == null || !firstDate.equals(dataBounds.first))) {
+ return false;
+ }
+ if (lastDate != dataBounds.last && (lastDate == null || !lastDate.equals(dataBounds.last))) {
+ return false;
+ }
+ return true;
+ }
+
/* (non-Javadoc)
* @see org.eclipsetrader.ui.charts.IChartObject#invalidate()
*/
@Override
public void invalidate() {
this.valid = false;
+ this.values = null;
+ this.firstDate = null;
+ this.lastDate = null;
}
/* (non-Javadoc)
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/HistogramBarChart.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/HistogramBarChart.java
index 70dbe046c..531c27303 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/HistogramBarChart.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/HistogramBarChart.java
@@ -21,6 +21,7 @@
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipsetrader.core.charts.IDataSeries;
+import org.eclipsetrader.core.charts.ScalarDownsampler;
/**
* Draw an historgram bar chart.
@@ -32,10 +33,13 @@ public class HistogramBarChart implements IChartObject, ISummaryBarDecorator, IA
private IDataSeries dataSeries;
private int width = 5;
- private RGB positiveColor = new RGB(0, 254, 0);
- private RGB negativeColor = new RGB(254, 0, 0);
+ private RGB positiveColor = ChartThemes.getDefault().getPositive();
+ private RGB negativeColor = ChartThemes.getDefault().getNegative();
private IAdaptable[] values;
+ private Date firstDate;
+ private Date lastDate;
+ private int pixelWidth;
private List pointArray = new ArrayList(2048);
private boolean valid;
private boolean hasFocus;
@@ -58,6 +62,11 @@ public HistogramBarChart(IDataSeries dataSeries) {
*/
@Override
public void setDataBounds(DataBounds dataBounds) {
+ this.width = dataBounds.horizontalSpacing - 1;
+ if (isSameRange(dataBounds)) {
+ return;
+ }
+
List l = new ArrayList(2048);
for (IAdaptable value : dataSeries.getValues()) {
Date date = (Date) value.getAdapter(Date.class);
@@ -65,17 +74,36 @@ public void setDataBounds(DataBounds dataBounds) {
l.add(value);
}
}
- this.values = l.toArray(new IAdaptable[l.size()]);
- this.width = dataBounds.horizontalSpacing - 1;
+ IAdaptable[] visible = l.toArray(new IAdaptable[l.size()]);
+ this.values = ScalarDownsampler.downsample(visible, dataBounds.width);
+ this.firstDate = dataBounds.first;
+ this.lastDate = dataBounds.last;
+ this.pixelWidth = dataBounds.width;
this.valid = false;
}
+ private boolean isSameRange(DataBounds dataBounds) {
+ if (values == null || pixelWidth != dataBounds.width) {
+ return false;
+ }
+ if (firstDate != dataBounds.first && (firstDate == null || !firstDate.equals(dataBounds.first))) {
+ return false;
+ }
+ if (lastDate != dataBounds.last && (lastDate == null || !lastDate.equals(dataBounds.last))) {
+ return false;
+ }
+ return true;
+ }
+
/* (non-Javadoc)
* @see org.eclipsetrader.ui.charts.IChartObject#invalidate()
*/
@Override
public void invalidate() {
this.valid = false;
+ this.values = null;
+ this.firstDate = null;
+ this.lastDate = null;
}
/* (non-Javadoc)
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/LineChart.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/LineChart.java
index 6826a3657..97968ac3b 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/LineChart.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/LineChart.java
@@ -22,6 +22,7 @@
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipsetrader.core.charts.IDataSeries;
+import org.eclipsetrader.core.charts.ScalarDownsampler;
/**
* Draws a line chart.
@@ -33,10 +34,13 @@ public class LineChart implements IChartObject, ISummaryBarDecorator, IAdaptable
private IDataSeries dataSeries;
private LineStyle style;
- private RGB color;
+ private RGB color = ChartThemes.getDefault().getLine();
private int width = 5;
private IAdaptable[] values;
+ private Date firstDate;
+ private Date lastDate;
+ private int pixelWidth;
private Point[] pointArray;
private boolean valid;
private boolean hasFocus;
@@ -52,7 +56,9 @@ public static enum LineStyle {
public LineChart(IDataSeries dataSeries, LineStyle style, RGB color) {
this.dataSeries = dataSeries;
this.style = style;
- this.color = color;
+ if (color != null) {
+ this.color = color;
+ }
numberFormat.setGroupingUsed(true);
numberFormat.setMinimumIntegerDigits(1);
@@ -73,6 +79,11 @@ public void setColor(RGB color) {
*/
@Override
public void setDataBounds(DataBounds dataBounds) {
+ this.width = dataBounds.horizontalSpacing;
+ if (isSameRange(dataBounds)) {
+ return;
+ }
+
List l = new ArrayList(2048);
for (IAdaptable value : dataSeries.getValues()) {
Date date = (Date) value.getAdapter(Date.class);
@@ -80,11 +91,27 @@ public void setDataBounds(DataBounds dataBounds) {
l.add(value);
}
}
- this.values = l.toArray(new IAdaptable[l.size()]);
- this.width = dataBounds.horizontalSpacing;
+ IAdaptable[] visible = l.toArray(new IAdaptable[l.size()]);
+ this.values = ScalarDownsampler.downsample(visible, dataBounds.width);
+ this.firstDate = dataBounds.first;
+ this.lastDate = dataBounds.last;
+ this.pixelWidth = dataBounds.width;
this.valid = false;
}
+ private boolean isSameRange(DataBounds dataBounds) {
+ if (values == null || pixelWidth != dataBounds.width) {
+ return false;
+ }
+ if (firstDate != dataBounds.first && (firstDate == null || !firstDate.equals(dataBounds.first))) {
+ return false;
+ }
+ if (lastDate != dataBounds.last && (lastDate == null || !lastDate.equals(dataBounds.last))) {
+ return false;
+ }
+ return true;
+ }
+
/* (non-Javadoc)
* @see org.eclipsetrader.ui.charts.IChartObject#handleFocusGained(org.eclipsetrader.ui.charts.ChartObjectFocusEvent)
*/
@@ -111,6 +138,9 @@ protected boolean hasFocus() {
@Override
public void invalidate() {
this.valid = false;
+ this.values = null;
+ this.firstDate = null;
+ this.lastDate = null;
}
/* (non-Javadoc)
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/OHLCLineChart.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/OHLCLineChart.java
index b0ddac3b1..d94c157fe 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/OHLCLineChart.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/charts/OHLCLineChart.java
@@ -23,6 +23,7 @@
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipsetrader.core.charts.IDataSeries;
+import org.eclipsetrader.core.charts.OHLCDownsampler;
import org.eclipsetrader.core.feed.IOHLC;
/**
@@ -35,10 +36,13 @@ public class OHLCLineChart implements IChartObject, ISummaryBarDecorator, IAdapt
private IDataSeries dataSeries;
private LineStyle style;
- private RGB color;
+ private RGB color = ChartThemes.getDefault().getLine();
private int width = 5;
private IAdaptable[] values;
+ private Date firstDate;
+ private Date lastDate;
+ private int pixelWidth;
private Point[] pointArray;
private boolean valid;
private boolean hasFocus;
@@ -56,7 +60,9 @@ public static enum LineStyle {
public OHLCLineChart(IDataSeries dataSeries, LineStyle style, RGB color) {
this.dataSeries = dataSeries;
this.style = style;
- this.color = color;
+ if (color != null) {
+ this.color = color;
+ }
numberFormat.setGroupingUsed(true);
numberFormat.setMinimumIntegerDigits(1);
@@ -77,6 +83,11 @@ public void setColor(RGB color) {
*/
@Override
public void setDataBounds(DataBounds dataBounds) {
+ this.width = dataBounds.horizontalSpacing;
+ if (isSameRange(dataBounds)) {
+ return;
+ }
+
List l = new ArrayList(2048);
for (IAdaptable value : dataSeries.getValues()) {
Date date = (Date) value.getAdapter(Date.class);
@@ -84,11 +95,38 @@ public void setDataBounds(DataBounds dataBounds) {
l.add(value);
}
}
- this.values = l.toArray(new IAdaptable[l.size()]);
- this.width = dataBounds.horizontalSpacing;
+ IAdaptable[] visible = l.toArray(new IAdaptable[l.size()]);
+ this.values = OHLCDownsampler.downsample(visible, dataBounds.width);
+ this.firstDate = dataBounds.first;
+ this.lastDate = dataBounds.last;
+ this.pixelWidth = dataBounds.width;
this.valid = false;
}
+ private boolean isSameRange(DataBounds dataBounds) {
+ if (values == null || pixelWidth != dataBounds.width) {
+ return false;
+ }
+ if (firstDate != dataBounds.first && (firstDate == null || !firstDate.equals(dataBounds.first))) {
+ return false;
+ }
+ if (lastDate != dataBounds.last && (lastDate == null || !lastDate.equals(dataBounds.last))) {
+ return false;
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipsetrader.ui.charts.IChartObject#invalidate()
+ */
+ @Override
+ public void invalidate() {
+ this.valid = false;
+ this.values = null;
+ this.firstDate = null;
+ this.lastDate = null;
+ }
+
/* (non-Javadoc)
* @see org.eclipsetrader.ui.charts.IChartObject#handleFocusGained(org.eclipsetrader.ui.charts.ChartObjectFocusEvent)
*/
@@ -109,14 +147,6 @@ protected boolean hasFocus() {
return hasFocus;
}
- /* (non-Javadoc)
- * @see org.eclipsetrader.ui.charts.IChartObject#invalidate()
- */
- @Override
- public void invalidate() {
- this.valid = false;
- }
-
/* (non-Javadoc)
* @see org.eclipsetrader.ui.charts.IChartObject#paint(org.eclipsetrader.ui.charts.IGraphics)
*/
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/TraderPerspective.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/TraderPerspective.java
index 4d48a3ee5..0441aa6b0 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/TraderPerspective.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/TraderPerspective.java
@@ -49,6 +49,8 @@ public void createInitialLayout(IPageLayout layout) {
// Right.
IPlaceholderFolderLayout right = layout.createPlaceholderFolder("right", IPageLayout.RIGHT, (float) 0.75, UIConstants.EDITOR_AREA); //$NON-NLS-1$
right.addPlaceholder("org.eclipsetrader.ui.views.level2:*"); //$NON-NLS-1$
+ right.addPlaceholder("org.eclipsetrader.ui.views.orders:*"); //$NON-NLS-1$
+ right.addPlaceholder("org.eclipsetrader.ui.views.tickers:*"); //$NON-NLS-1$
// Add "new wizards".
layout.addNewWizardShortcut("org.eclipsetrader.ui.wizards.new.stock");//$NON-NLS-1$
@@ -59,8 +61,17 @@ public void createInitialLayout(IPageLayout layout) {
layout.addShowViewShortcut("org.eclipsetrader.ui.views.navigator"); //$NON-NLS-1$
layout.addShowViewShortcut("org.eclipsetrader.ui.views.markets"); //$NON-NLS-1$
layout.addShowViewShortcut("org.eclipsetrader.ui.views.repositories"); //$NON-NLS-1$
+ layout.addShowViewShortcut("org.eclipsetrader.ui.views.watchlist"); //$NON-NLS-1$
+ layout.addShowViewShortcut("org.eclipsetrader.ui.views.level2"); //$NON-NLS-1$
+ layout.addShowViewShortcut("org.eclipsetrader.ui.views.orders"); //$NON-NLS-1$
+ layout.addShowViewShortcut("org.eclipsetrader.ui.views.tickers"); //$NON-NLS-1$
// Add default action sets
layout.addActionSet("org.eclipsetrader.ui.launcher");
+ layout.addActionSet("org.eclipsetrader.ui.charts.tools");
+ layout.addActionSet("org.eclipsetrader.ui.charts.zoom");
+
+ // Add "perspectives".
+ layout.addPerspectiveShortcut("org.eclipsetrader.ui.charts"); //$NON-NLS-1$
}
}
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/ChartsPerspective.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/ChartsPerspective.java
index 4501929fc..4ef8ee3ea 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/ChartsPerspective.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/ChartsPerspective.java
@@ -49,8 +49,16 @@ public void createInitialLayout(IPageLayout layout) {
layout.addView("org.eclipsetrader.ui.charts.palette", IPageLayout.BOTTOM, (float) 0.50, "left"); //$NON-NLS-1$ //$NON-NLS-2$
// Bottom
- IPlaceholderFolderLayout bottom = layout.createPlaceholderFolder("bottom", IPageLayout.BOTTOM, (float) 0.75, UIConstants.EDITOR_AREA); //$NON-NLS-1$
+ IFolderLayout bottom = layout.createFolder("bottom", IPageLayout.BOTTOM, (float) 0.75, UIConstants.EDITOR_AREA); //$NON-NLS-1$
+ bottom.addView("org.eclipsetrader.ui.views.markets"); //$NON-NLS-1$
bottom.addPlaceholder("org.eclipse.ui.views.ProgressView"); //$NON-NLS-1$
+ bottom.addPlaceholder("org.eclipsetrader.ui.views.orders:*"); //$NON-NLS-1$
+
+ // Right.
+ IPlaceholderFolderLayout right = layout.createPlaceholderFolder("right", IPageLayout.RIGHT, (float) 0.75, UIConstants.EDITOR_AREA); //$NON-NLS-1$
+ right.addPlaceholder("org.eclipsetrader.ui.views.level2:*"); //$NON-NLS-1$
+ right.addPlaceholder("org.eclipsetrader.ui.views.tickers:*"); //$NON-NLS-1$
+ right.addPlaceholder("org.eclipsetrader.ui.views.watchlist:*"); //$NON-NLS-1$
// Add "new wizards".
layout.addNewWizardShortcut("org.eclipsetrader.ui.wizards.new.security");//$NON-NLS-1$
@@ -64,6 +72,10 @@ public void createInitialLayout(IPageLayout layout) {
layout.addShowViewShortcut("org.eclipsetrader.ui.views.navigator"); //$NON-NLS-1$
layout.addShowViewShortcut("org.eclipsetrader.ui.views.markets"); //$NON-NLS-1$
layout.addShowViewShortcut("org.eclipsetrader.ui.views.repositories"); //$NON-NLS-1$
+ layout.addShowViewShortcut("org.eclipsetrader.ui.views.watchlist"); //$NON-NLS-1$
+ layout.addShowViewShortcut("org.eclipsetrader.ui.views.level2"); //$NON-NLS-1$
+ layout.addShowViewShortcut("org.eclipsetrader.ui.views.orders"); //$NON-NLS-1$
+ layout.addShowViewShortcut("org.eclipsetrader.ui.views.tickers"); //$NON-NLS-1$
// Add "perspectives".
layout.addPerspectiveShortcut("org.eclipsetrader.ui.traderPerspective"); //$NON-NLS-1$
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/ChartViewPart.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/ChartViewPart.java
index fb59946b3..ea456ad98 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/ChartViewPart.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/ChartViewPart.java
@@ -96,6 +96,7 @@
import org.eclipsetrader.ui.charts.ChartViewItem;
import org.eclipsetrader.ui.charts.IChartEditorListener;
import org.eclipsetrader.ui.charts.IChartObject;
+import org.eclipsetrader.ui.charts.IChartObjectFactory;
import org.eclipsetrader.ui.internal.UIActivator;
import org.eclipsetrader.ui.internal.charts.DataImportJob;
import org.eclipsetrader.ui.internal.charts.ImportDataPage;
@@ -149,6 +150,11 @@ public class ChartViewPart extends ViewPart implements ISaveablePart {
private CurrentBookFactory currentBookFactory;
private TradeFactory tradeFactory;
+ private Action candleAction;
+ private Action barAction;
+ private Action lineAction;
+ private Action histogramAction;
+
IMemento memento;
IPreferenceStore preferenceStore;
@@ -335,7 +341,18 @@ public void init(IViewSite site, IMemento memento) throws PartInitException {
IToolBarManager toolBarManager = actionBars.getToolBarManager();
toolBarManager.add(new Separator("additions")); //$NON-NLS-1$
toolBarManager.add(updateAction);
-
+ toolBarManager.add(new Separator());
+ toolBarManager.add(candleAction);
+ toolBarManager.add(barAction);
+ toolBarManager.add(lineAction);
+ toolBarManager.add(histogramAction);
+ toolBarManager.add(new Separator());
+ if (periodActions != null) {
+ for (int i = 0; i < periodActions.length; i++) {
+ toolBarManager.add(periodActions[i]);
+ }
+ }
+
if (dialogSettings != null) {
TimeSpan periodTimeSpan = TimeSpan.fromString(dialogSettings.get(K_PERIOD));
TimeSpan resolutionTimeSpan = TimeSpan.fromString(dialogSettings.get(K_RESOLUTION));
@@ -471,7 +488,7 @@ public void run() {
public void run() {
}
};
- pasteAction.setId("copy"); //$NON-NLS-1$
+ pasteAction.setId("paste"); //$NON-NLS-1$
pasteAction.setActionDefinitionId("org.eclipse.ui.edit.paste"); //$NON-NLS-1$
pasteAction.setImageDescriptor(sharedImages.getImageDescriptor(ISharedImages.IMG_TOOL_PASTE));
pasteAction.setDisabledImageDescriptor(sharedImages.getImageDescriptor(ISharedImages.IMG_TOOL_PASTE_DISABLED));
@@ -556,6 +573,61 @@ public void run() {
}
}
};
+
+ candleAction = new Action("Candlestick", IAction.AS_RADIO_BUTTON) {
+
+ @Override
+ public void run() {
+ switchChartType(MainRenderStyle.Candles);
+ }
+ };
+ candleAction.setToolTipText(Messages.ChartViewPart_CandlestickAction);
+
+ barAction = new Action("OHLC Bars", IAction.AS_RADIO_BUTTON) {
+
+ @Override
+ public void run() {
+ switchChartType(MainRenderStyle.Bars);
+ }
+ };
+ barAction.setToolTipText(Messages.ChartViewPart_BarAction);
+
+ lineAction = new Action("Line", IAction.AS_RADIO_BUTTON) {
+
+ @Override
+ public void run() {
+ switchChartType(MainRenderStyle.Line);
+ }
+ };
+ lineAction.setToolTipText(Messages.ChartViewPart_LineChartAction);
+
+ histogramAction = new Action("Area", IAction.AS_RADIO_BUTTON) {
+
+ @Override
+ public void run() {
+ switchChartType(MainRenderStyle.Histogram);
+ }
+ };
+ histogramAction.setToolTipText(Messages.ChartViewPart_HistogramAction);
+ }
+
+ void switchChartType(MainRenderStyle style) {
+ IViewItem[] rows = view.getItems();
+ for (int i = 0; i < rows.length; i++) {
+ ChartRowViewItem rowItem = (ChartRowViewItem) rows[i];
+ IViewItem[] children = rowItem.getItems();
+ if (children != null) {
+ for (int j = 0; j < children.length; j++) {
+ ChartViewItem viewItem = (ChartViewItem) children[j];
+ IChartObjectFactory factory = viewItem.getFactory();
+ if (factory instanceof MainChartFactory) {
+ ((MainChartFactory) factory).setStyle(style);
+ rowItem.refresh();
+ }
+ }
+ }
+ }
+ refreshChart();
}
/* (non-Javadoc)
@@ -658,6 +730,23 @@ void createContextMenu() {
@Override
public void menuAboutToShow(IMenuManager menuManager) {
menuManager.add(new Separator("top")); //$NON-NLS-1$
+
+ MenuManager chartTypeMenu = new MenuManager(Messages.ChartViewPart_ChartTypeMenu);
+ chartTypeMenu.add(candleAction);
+ chartTypeMenu.add(barAction);
+ chartTypeMenu.add(lineAction);
+ chartTypeMenu.add(histogramAction);
+ menuManager.add(chartTypeMenu);
+
+ MenuManager periodMenu = new MenuManager(Messages.ChartViewPart_PeriodMenu);
+ if (periodActions != null) {
+ for (int i = 0; i < periodActions.length; i++) {
+ periodMenu.add(periodActions[i]);
+ }
+ }
+ menuManager.add(periodMenu);
+
+ menuManager.add(new Separator());
menuManager.add(cutAction);
menuManager.add(copyAction);
menuManager.add(pasteAction);
@@ -1024,8 +1113,28 @@ public int compare(Period o1, Period o2) {
periodActions[i] = new ContributionItem(list.get(i));
}
} catch (Exception e) {
+ periodActions = createDefaultPeriodActions();
e.printStackTrace();
}
+ if (periodActions == null) {
+ periodActions = createDefaultPeriodActions();
+ }
+ }
+
+ private ContributionItem[] createDefaultPeriodActions() {
+ PeriodList list = new PeriodList();
+ list.add(new Period("2 Years", TimeSpan.years(2), TimeSpan.days(1)));
+ list.add(new Period("1 Year", TimeSpan.years(1), TimeSpan.days(1)));
+ list.add(new Period("6 Months", TimeSpan.months(6), TimeSpan.days(1)));
+ list.add(new Period("3 Months", TimeSpan.months(3), TimeSpan.days(1)));
+ list.add(new Period("1 Month", TimeSpan.months(1), TimeSpan.days(1)));
+ list.add(new Period("5 Days", TimeSpan.days(5), TimeSpan.minutes(5)));
+ list.add(new Period("1 Day", TimeSpan.days(1), TimeSpan.minutes(1)));
+ ContributionItem[] actions = new ContributionItem[list.size()];
+ for (int i = 0; i < actions.length; i++) {
+ actions[i] = new ContributionItem(list.get(i));
+ }
+ return actions;
}
public void setPeriodActionSelection(TimeSpan period, TimeSpan resolution) {
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/MainChartFactory.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/MainChartFactory.java
index f57d19f9b..1f4d425a8 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/MainChartFactory.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/MainChartFactory.java
@@ -184,7 +184,7 @@ public IChartParameters getParameters() {
*/
@Override
public void setParameters(IChartParameters parameters) {
- style = parameters.hasParameter("style") ? MainRenderStyle.getStyleFromName(parameters.getString("style")) : MainRenderStyle.Bars;
+ style = parameters.hasParameter("style") ? MainRenderStyle.getStyleFromName(parameters.getString("style")) : MainRenderStyle.Candles;
lineColor = parameters.getColor("line-color");
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/MainPropertiesPage.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/MainPropertiesPage.java
index 6692541a4..b2f8949d8 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/MainPropertiesPage.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/MainPropertiesPage.java
@@ -15,7 +15,6 @@
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
@@ -23,6 +22,7 @@
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.dialogs.PropertyPage;
+import org.eclipsetrader.ui.charts.ChartThemes;
public class MainPropertiesPage extends PropertyPage {
@@ -77,7 +77,7 @@ public void widgetSelected(SelectionEvent e) {
label = new Label(content, SWT.NONE);
label.setText("Line");
lineColor = new ColorSelector(content);
- lineColor.setColorValue(new RGB(0, 0, 255));
+ lineColor.setColorValue(ChartThemes.getDefault().getLine());
lineColor.getButton().setData("label", label);
label = new Label(content, SWT.NONE);
label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
@@ -85,7 +85,7 @@ public void widgetSelected(SelectionEvent e) {
label = new Label(content, SWT.NONE);
label.setText("Bars");
barPositiveColor = new ColorSelector(content);
- barPositiveColor.setColorValue(new RGB(0, 0, 255));
+ barPositiveColor.setColorValue(ChartThemes.getDefault().getPositive());
barPositiveColor.getButton().setData("label", label);
label = new Label(content, SWT.NONE);
label.setText("Positive");
@@ -94,7 +94,7 @@ public void widgetSelected(SelectionEvent e) {
label = new Label(content, SWT.NONE);
barNegativeColor = new ColorSelector(content);
- barNegativeColor.setColorValue(new RGB(0, 0, 255));
+ barNegativeColor.setColorValue(ChartThemes.getDefault().getNegative());
barNegativeColor.getButton().setData("label", label);
label = new Label(content, SWT.NONE);
label.setText("Negative");
@@ -104,7 +104,7 @@ public void widgetSelected(SelectionEvent e) {
label = new Label(content, SWT.NONE);
label.setText("Candles");
candlePositiveColor = new ColorSelector(content);
- candlePositiveColor.setColorValue(new RGB(0, 0, 255));
+ candlePositiveColor.setColorValue(ChartThemes.getDefault().getPositive());
candlePositiveColor.getButton().setData("label", label);
label = new Label(content, SWT.NONE);
label.setText("Positive");
@@ -113,7 +113,7 @@ public void widgetSelected(SelectionEvent e) {
label = new Label(content, SWT.NONE);
candleNegativeColor = new ColorSelector(content);
- candleNegativeColor.setColorValue(new RGB(0, 0, 255));
+ candleNegativeColor.setColorValue(ChartThemes.getDefault().getNegative());
candleNegativeColor.getButton().setData("label", label);
label = new Label(content, SWT.NONE);
label.setText("Negative");
@@ -122,7 +122,7 @@ public void widgetSelected(SelectionEvent e) {
label = new Label(content, SWT.NONE);
candleOutlineColor = new ColorSelector(content);
- candleOutlineColor.setColorValue(new RGB(0, 0, 255));
+ candleOutlineColor.setColorValue(ChartThemes.getDefault().getOutline());
candleOutlineColor.getButton().setData("label", label);
label = new Label(content, SWT.NONE);
label.setText("Outline");
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/Messages.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/Messages.java
index d11dcb6de..0b3235077 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/Messages.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/Messages.java
@@ -26,6 +26,12 @@ public class Messages extends NLS {
public static String ChartViewPart_UpdateAction;
public static String ChartViewPart_ZoomInAction;
public static String ChartViewPart_ZoomOutAction;
+ public static String ChartViewPart_CandlestickAction;
+ public static String ChartViewPart_BarAction;
+ public static String ChartViewPart_LineChartAction;
+ public static String ChartViewPart_HistogramAction;
+ public static String ChartViewPart_ChartTypeMenu;
+ public static String ChartViewPart_PeriodMenu;
public static String CurrentBookFactory_Name;
public static String CurrentPriceLineFactory_Name;
public static String CustomPeriodDialog_BeginDateLabel;
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/messages.properties b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/messages.properties
index 43766ac1d..220417068 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/messages.properties
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/charts/views/messages.properties
@@ -19,6 +19,12 @@ ChartViewPart_ShowCurrentPriceAction=Show current price
ChartViewPart_UpdateAction=Update
ChartViewPart_ZoomInAction=Zoom-In
ChartViewPart_ZoomOutAction=Zoom-Out
+ChartViewPart_CandlestickAction=Candlestick
+ChartViewPart_BarAction=OHLC Bars
+ChartViewPart_LineChartAction=Line
+ChartViewPart_HistogramAction=Area
+ChartViewPart_ChartTypeMenu=Chart Type
+ChartViewPart_PeriodMenu=Period
CurrentBookFactory_Name=Current Price
CurrentPriceLineFactory_Name=Current Price
CustomPeriodDialog_BeginDateLabel=Begin Date
diff --git a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/views/Level2View.java b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/views/Level2View.java
index 41dcf18fd..6d9857c29 100644
--- a/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/views/Level2View.java
+++ b/org.eclipsetrader.ui/src/org/eclipsetrader/ui/internal/views/Level2View.java
@@ -346,6 +346,15 @@ public void linkExited(HyperlinkEvent e) {
activeConnector.setText(this.connector.getName());
}
}
+ if (this.connector == null) {
+ IFeedConnector connector = CoreActivator.getDefault().getDefaultConnector();
+ if (connector instanceof IFeedConnector2) {
+ this.connector = (IFeedConnector2) connector;
+ if (this.connector != null) {
+ activeConnector.setText(this.connector.getName());
+ }
+ }
+ }
if (s != null && connector != null) {
subscription = this.connector.subscribeLevel2(s);
diff --git a/org.jdom/.tycho-consumer-pom.xml b/org.jdom/.tycho-consumer-pom.xml
index d681ccd87..125ee927a 100644
--- a/org.jdom/.tycho-consumer-pom.xml
+++ b/org.jdom/.tycho-consumer-pom.xml
@@ -6,14 +6,6 @@
org.jdom
1.0.0-SNAPSHOT
JDOM (External)
- Delegates to Maven Central version of JDOM 2.x.
-
-
- net.sf.cglib
- net.sf.cglib
- 3.3.0
- compile
- false
-
-
+ Delegates to embedded jdom-1.1.3.jar.
+
diff --git a/org.jdom/build.properties b/org.jdom/build.properties
index 9ac5b3415..5dcd575dc 100644
--- a/org.jdom/build.properties
+++ b/org.jdom/build.properties
@@ -1,2 +1,3 @@
+output.. = bin/
bin.includes = META-INF/,\
libs/jdom-1.1.3.jar