--- title: "Jam 08 - Exercise 3" tags: - 2 ๐Ÿ“ in writing - 3 ๐Ÿงช in testing - 4 ๐Ÿฅณ done --- <!-- markdownlint-disable line-length single-h1 no-inline-html --> <!-- markdownlint-configure-file { "ul-indent": { "indent": 4 }, "link-fragments": {"ignore_case": true} } --> {%hackmd dJZ5TulxSDKme-3fSY4Lbw %} # Exercise 3 - Data Visualization ## Overview - Exercise 3 Now that we have our basic application structure, we'll implement the data loading and visualization components. We'll start by loading our stock data, then connect it to our existing UI components. ## The Problem - Exercise 3 **Problem**: Create interactive visualizations that: - Display stock price trends over time using different chart types - Allow filtering by date range and stock symbols - Provide responsive UI feedback through property binding ## Required Steps - Exercise 3 ### Part 1 - Loading and Displaying Data First, let's connect our model to our actual data source: 1. **Create the Data File** First, let's get our historical stock data file: ```bash # You may need to create the directory first: mkdir -p src/main/resources/jam08 # If working on your laptop (replace userid with your Bucknell username): scp "userid@linuxremote.bucknell.edu:/home/csci205/2025-spring/student/jam08/historical_stocks.csv" src/main/resources/jam08/ # If working on linuxremote: cp "/home/csci205/2025-spring/student/jam08/historical_stocks.csv" src/main/resources/jam08/ ``` This file contains real historical stock data for several companies including: - Volkswagen (VOW3.DE) - Meta (META) - Amazon (AMZN) - Tesla (TSLA) - GameStop (GME) The data includes: - Daily price information (open, high, low, close) - Trading volume - Data spanning several months This rich dataset will allow us to create meaningful visualizations and demonstrate various JavaFX features. :::info ๐Ÿ”ง **CSV File Structure** Each row in the file contains: - Date (YYYY-MM-DD format) - Symbol (stock ticker) - Opening price - High price for the day - Low price for the day - Closing price - Trading volume This structure matches our `Stock` class design from Exercise 1. ::: 2. **Load Data in the Controller** Let's enhance our `StockController` to handle data loading. We need to: :::info ๐Ÿ”ง **Requirements** The controller should: - Load initial stock data when created - Update the view with success/failure feedback - Handle errors gracefully with user-friendly messages ::: First, let's add a new method to handle data loading and call it from your `initalize` method. Complete the TODO comments: ```java private void loadInitialData() { // TODO: Load the stock data file using the model // TODO: Alert user with a success message with record count // TODO: Handle potential errors: } ``` Key implementation points to consider: 1. **Error Handling** - What types of exceptions might occur? - How should each type be handled? - What feedback should users receive? 2. **User Feedback** - How will users know if data loaded successfully? - What information is useful to display? - Where should feedback appear in the UI? 3. **JavaFX Alert Dialogs** - When should alerts be shown? - What alert types are appropriate? - What information should alerts contain? :::warning ๐Ÿšง **Common Pitfalls** - Not handling all possible exceptions - Showing technical error messages to users - Missing user feedback for successful operations - Not cleaning up resources properly ::: Try implementing the `loadInitialData()` method following these guidelines. Remember to: - Use try-catch blocks appropriately - Provide clear user feedback - Follow JavaFX best practices for alerts - Consider the user experience After implementing data loading, we'll connect it to the view components in the next step. 3. **Connect Data to View Components** The `StockView` needs methods to display our data. Let's add them step by step: a. First, add getters for our view components: ```java public TableView<Stock> getStockTable() { return stockTable; } public LineChart<String, Number> getPriceChart() { return priceChart; } ``` b. Then update the setupTable to load the data: ```java private void setupTable() { stockTable = new TableView<>(); // TODO: Create table columns for each Stock field // Hint 1: Use TableColumn<Stock, T> where T matches the field type // Hint 2: Use PropertyValueFactory to bind column to Stock property // Example for date column: TableColumn<Stock, LocalDate> dateCol = new TableColumn<>("Date"); dateCol.setCellValueFactory(new PropertyValueFactory<>("date")); // TODO: Create columns for: // - Symbol (String) // - Open Price (BigDecimal) // - High Price (BigDecimal) // - Low Price (BigDecimal) // - Close Price (BigDecimal) // - Volume (Long) // TODO: Add all columns to the table // Hint: Use stockTable.getColumns().addAll(List.of(...)); root.setBottom(stockTable); } ``` c. Then create methods to populate them: ```java public void populateTable(List<Stock> stocks) { // TODO: Convert the list to an ObservableList and set the items in the table // Hint: Use FXCollections.observableArrayList() } public void populateChart(List<Stock> stocks) { // TODO: Create a new XYChart.Series for stock prices /* TODO: Add data points from the stocks list (you can use streams! or other iterative ways) Hint1: Set the key of the hashmap to the symbol in order to group stocks Hint2: Set the value of the hashmap to an XYChart.Series Hint3: XYChart.Series Needs a <String, Number> Hint4: The string is the xaxis (date). Use stock.getDate().toString() Hint5: The Number is the yaxis (date). Use stock.getClosePrice() but getClosePrice() returns a BigNumber. What do? */ // TODO: clear the `priceChart` data and add all series } ``` d. Finally, update the `StockController` to use these methods: ```java private void initialize() { // Any existing method calls // TODO: Populate the table using view.populateTable() // TODO: Populate the chart using view.populateChart() // setupEventHandlers(); // TODO But Not Yet // setupBindings(); // TODO But Not Yet } ``` You should now have a chart! ![Initial-Stock-Price-History](https://www.dropbox.com/scl/fi/jplbyutb3mfa9a61qvhpr/Initial-Stock-Price-History.png?rlkey=7f7d029t40l22h12iwb6qmudy&raw=1) :::info ๐Ÿ”ง Implementation Tips - Use FXCollections.observableArrayList() to create observable lists for JavaFX - Remember to handle null checks when working with data - Consider using property binding for automatic updates - Think about error handling and user feedback ::: ### Part 2 - Enhancing the Chart Enhance your chart with professional features and interactivity. Implement the following features in your `StockView` class and call them from `initializeView`: 1. **Configure Chart Properties** ```java private void enhanceChart() { // TODO: Enable animations and set duration // Hint: Look at LineChart API for animation-related methods // Hint: Search for methods starting with 'set' and containing 'animation' // Hint: Animation duration is in milliseconds (500-1000ms recommended) // TODO: Configure axes with proper labels and formatting // Hint: Use NumberAxis and CategoryAxis methods for axis configuration // Hint: Look for methods like setLabel(), setTickUnit(), setMinorTickVisible() // Hint: Consider using setAutoRanging() for dynamic axis ranges // TODO: Set up legend and grid lines // Hint: Search LineChart API for legend and grid-related methods // Hint: Look for methods containing 'legend' and 'grid' } ``` 2. **Add Interactive Features** ```java private void addTooltips() { // TODO: Create tooltips for data points // Hint: Use JavaFX's Tooltip class // Hint: Look at Tooltip.install() method // Hint: Format tooltip text using String.format() // TODO: Format tooltip content // Hint: Include date, symbol, price, and volume information // Hint: Use String.format() for clean formatting // Hint: Consider using BigDecimal's toString() for precise price display // TODO: Style tooltips // Hint: Use CSS to style tooltips - You will add CSS Styling // in the next step so you might want to come back to this // Hint: Look for .tooltip selector in JavaFX CSS reference } private void setupZoomAndPan() { // TODO: Enable zoom functionality // Hint: Look at LineChart's zoom-related methods // Hint: Search for methods containing 'zoom' or 'scale' // Hint: Consider using setOnScroll() for zoom behavior // TODO: Add pan controls // Hint: Use setOnMouseDragged() for pan behavior // Hint: Look at translateX and translateY properties // Hint: Consider using setOnMousePressed() to track drag start // TODO: Implement zoom reset // Hint: Create a reset button or keyboard shortcut // Hint: Look for methods to reset axis ranges } ``` 3. **Add CSS Styling** Create `styles.css` in your jam08/resources directory: :::info ๐Ÿ“š **JavaFX CSS Reference** For detailed information about styling JavaFX applications with CSS, refer to Oracle's official JavaFX CSS tutorial: [JavaFX CSS Reference Guide](https://docs.oracle.com/javafx/2/css_tutorial/jfxpub-css_tutorial.htm) This tutorial covers: - Basic CSS syntax for JavaFX - Common style properties - Selector types - Style inheritance - Best practices ::: ```css .chart { /* TODO: Configure chart appearance */ /* Hint: Look at JavaFX CSS reference for chart styling */ /* Hint: Consider background, padding, and border properties */ } .chart-series-line { /* TODO: Style series lines */ /* Hint: Use -fx-stroke for line color */ /* Hint: Use -fx-stroke-width for line thickness */ } .chart-line-symbol { /* TODO: Style data point symbols */ /* Hint: Use -fx-background-color for symbol color */ /* Hint: Use -fx-shape for custom symbols */ } .axis { /* TODO: Style axes */ /* Hint: Use -fx-tick-label-fill for label color */ /* Hint: Use -fx-tick-mark-visible for tick marks */ } .axis-label { /* TODO: Style axis labels */ /* Hint: Use -fx-font for label font */ /* Hint: Use -fx-text-fill for label color */ } ``` :::info ๐Ÿ”ง **CSS Tips** - JavaFX CSS uses `-fx-` prefix for all properties - Colors can be specified using: - Hex codes (e.g., `#2c3e50`) - RGB values (e.g., `rgb(44, 62, 80)`) - Color names (e.g., `white`, `black`) - Font properties include: - `-fx-font-family` - `-fx-font-size` - `-fx-font-weight` - Layout properties include: - `-fx-padding` - `-fx-margin` - `-fx-border-*` To apply your CSS styles to your JavaFX application: 1. In your `StockVisualizerApp` class, add this line after creating the scene: ```java scene.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/jam08/styles.css")).toExternalForm()); ``` 2. Make sure your CSS file is in the correct location: ```text src/main/resources/jam08/styles.css ``` 3. Verify the CSS is loaded by running your application - you should see your styles applied to the chart and other components. If your styles aren't appearing: - Check the file path is correct - Verify the CSS file is being copied to the build directory - Make sure there are no syntax errors in your CSS - Try cleaning and rebuilding your project ::: > ๐Ÿ” **Checkpoint**: Chart Enhancements > > - Chart displays multiple data series > - Interactive features work as expected > - Visual styling is professional > - Performance remains acceptable > - All tests pass ### Part 3 - Filtering :::info ๐Ÿ”„ **Iterative Development** As we implement filtering functionality, we discover we need to modify code we wrote earlier. This is a common occurrence in software development! Requirements often emerge or become clearer as we implement new features. Before implementing filtering, we need to: 1. Return to the `Stock` class from Exercise 1 2. Add a getter method for the symbol field if you haven't already: ```java public String getSymbol() { return symbol; } ``` 3. Verify your tests still pass after this modification This illustrates an important principle in software engineering: development is iterative. We often need to revisit and enhance earlier code as our understanding of the system grows or as new features require additional functionality. ::: To implement filtering, we'll need to: 1. **Add Filtering Controls to StockView** First, add these fields to your `StockView` class: ```java public class StockView { private BorderPane root; private LineChart<String, Number> priceChart; private TableView<Stock> stockTable; // Add these new fields for filtering private DatePicker startDatePicker; // DatePicker for selecting the start date of the filter private DatePicker endDatePicker; // DatePicker for selecting the end date of the filter private ComboBox<String> symbolComboBox; // ComboBox for selecting the stock symbol to filter by // ... existing code ... } ``` Then add getters for these fields using IntelliJ's generator feature. 2. **Update the Controls Setup** In your `setupControls()` method, add the filtering controls: ```java private void setupControls() { // Existing code // TODO: Create a Label for the date range filter // TODO: Initialize startDatePicker and endDatePicker // TODO: Create a Label for the stock symbol filter // TODO: Initialize symbolComboBox // TODO: Add all controls to the VBox // Hint: Use controls.getChildren().addAll(...); // TODO: Set the controls VBox to the left side of the root BorderPane // Hint: Use root.setLeft(controls); } ``` 3. **Implement Filtering Logic** In your `StockViewController` class, add these methods: ```java private void setupFiltering() { // TODO: Get references to the controls from the view // Hint: Use view.getStartDatePicker(), view.getEndDatePicker(), and view.getSymbolComboBox() // TODO: Populate symbol combo box with unique symbols from the model // Hint: Use model.getUniqueSymbols() // TODO: Add listeners to update the view when filters change // Hint: Use valueProperty().addListener() on each control // Example: startDate.valueProperty().addListener((obs, oldVal, newVal) -> updateFilteredData()); // TODO: Add this method to your initialize method of this class } private void updateFilteredData() { // TODO: Get current filter values from the view // Hint: Use the getValue() methods in connection with the getters in your view // TODO: Get a list of stocks by the model, // filtered by dates if dates are selected // Hint: Use model.getStocksBySymbol() or model.getStocks() depending on the // contents of the filter // TODO: Filter your list by the symbol filter if a symbol is selected // Hint: You already wrote a similar method in the model that you could borrow some code from // TODO: Update the view with filtered data // Hint: Use view.populateTable(filteredStocks) and view.populateChart(filteredStocks) } ``` :::info ๐Ÿ”ง **Filtering Tips** - Use JavaFX's built-in filtering capabilities - Consider using `FXCollections.observableArrayList()` for data binding - Remember to handle null values in filters - Update both table and chart when filters change - Consider adding a "Clear Filters" button ::: :::info ๐Ÿ”‘ **Key Concepts** - Use JavaFX's built-in chart components effectively - Leverage Streams API for data transformation - Implement proper property binding - Handle real-time updates efficiently ::: > ๐Ÿ” **Checkpoint**: Exercise 3 > > - Data loads and displays correctly in both table and chart > - Chart displays multiple data series with proper styling > - Interactive features (tooltips, zoom/pan) work as expected > - Date range filter works correctly > - Symbol filter works correctly > - Filters can be combined > - UI updates immediately when filters change > - Performance remains acceptable with large datasets > - All tests pass ## Save Your Work - Exercise 3 Verify what files are uncommited: ```bash git status ``` Stage your changes: Use `git add` to add the appropriate files. Commit your work: ```bash git commit -m "jam08: Implement data visualization components" ```