Understanding DataModel Propagation
DataModel propagation enables interactive data synchronization across multiple UI components. When data changes in one component, these changes can be automatically reflected in other related components.
How Propagation Works
Propagation in DataModel follows these principles:
- A root DataModel instance can propagate changes to its derived DataModels
- Changes are propagated through a selection query that identifies affected rows
- Each derived DataModel receives the filtered data and can respond accordingly
- UI components connected to these DataModels can update to reflect the changes
Propagation is particularly useful for creating interactive dashboards where filtering or selecting data in one view should update other related views.
Basic Concepts
Selection Query
A criteria object that defines which data should be affected:
{
criteria: {
field: "Origin",
value: "USA",
operator: "eq"
},
fields: ["Origin"]
}
Propagation Handler
A callback function that receives the filtered DataModel and handles the UI update:
dataModel.onPropagation((filteredDm, info) => {
// Update UI with filtered data
});
Tutorial: Creating Connected Data Grids
Let's build an example with two connected grids that respond to the same filter operations.
Remember that the Grid component used in these examples is just for demonstration. The propagation concepts can be applied with any UI components that can render DataModel data. Most commonly, you will be using Muze for your data visualization needs in conjunction with DataModel.
Step 1: Create the Base DataModel
First, let's create our root DataModel:
const Datamodel = muze.DataModel;
const formattedData = await Datamodel.loadData(data, schema);
const rootDm = new Datamodel(formattedData);
Step 2: Create Derived DataModels
Create two different groupings of the data:
// Group by Origin
let groupedOrigin = rootDm.groupBy(["Origin"]);
// Group by Maker
let groupedMaker = rootDm.groupBy(["Maker"]);
The grouped data will look like this:
Grouped by Origin:
Origin | Miles_per_Gallon | Displacement | Horsepower | Weight_in_lbs | Acceleration |
---|---|---|---|---|---|
USA | 20.13 | 455 | 119.61 | 1800 | 14.93 |
Europe | 27.89 | 183 | 81 | 1825 | 16.82 |
Japan | 30.45 | 168 | 79.84 | 1613 | 16.17 |
Grouped by Maker:
Maker | Miles_per_Gallon | Displacement | Horsepower | Weight_in_lbs | Acceleration |
---|---|---|---|---|---|
chevrolet | 20.47 | 454 | 114.11 | 2035 | 15.23 |
buick | 19.18 | 455 | 136.41 | 2155 | 14.70 |
plymouth | 21.70 | 440 | 113.41 | 1875 | 14.72 |
Step 3: Set Up the UI
Create the HTML structure for our grids:
<div class="app-container d-flex flex-column">
<div id="input-toolbar"></div>
<div>
<div class="grid-1-sort-input"></div>
<div id="grid-1" class="grid"></div>
</div>
<div>
<div class="grid-2-sort-input"></div>
<div id="grid-2" class="grid"></div>
</div>
</div>
Step 4: Initialize the Grids
const g1 = new Grid("#grid-1");
const g2 = new Grid("#grid-2");
// Initial render
g1.render(groupedOrigin);
g2.render(groupedMaker);
Step 5: Set Up Propagation Handlers
Add handlers to respond to data changes:
// Handle updates for Origin grid
groupedOrigin.onPropagation((filteredDm, info) => {
g1.render(filteredDm);
});
// Handle updates for Maker grid
groupedMaker.onPropagation((filteredDm, info) => {
g2.render(filteredDm);
});
Step 6: Connect Filter Controls
Set up the filter toolbar and connect it to propagation:
// Initialize toolbar
initToolbar("#input-toolbar", rootDm.getData().schema);
// Add filter handler
addFilterEventListener(({ field, value, operator }) => {
rootDm.propagate(
[{
criteria: {
field: field,
value: value,
operator: Datamodel.ComparisonOperators[operator]
},
fields: [field]
}],
{
payload: { action: "filter" }
}
);
});
// Add reset handler
addResetEventListener(() => {
g1.render(groupedOrigin);
g2.render(groupedMaker);
});
Complete Implementation
const Datamodel = muze.DataModel;
// Create base DataModel
const formattedData = await Datamodel.loadData(data, schema);
let rootDm = new Datamodel(formattedData);
// Create grouped DataModels
let groupedOrigin = rootDm.groupBy(["Origin"]);
let groupedMaker = rootDm.groupBy(["Maker"]);
// Initialize grids
const g1 = new Grid("#grid-1");
const g2 = new Grid("#grid-2");
// Initial render
g1.render(groupedOrigin);
g2.render(groupedMaker);
// Initialize toolbar
initToolbar("#input-toolbar", rootDm.getData().schema);
// Set up propagation handlers
groupedOrigin.onPropagation((filteredDm, info) => {
g1.render(filteredDm);
});
groupedMaker.onPropagation((filteredDm, info) => {
g2.render(filteredDm);
});
// Add filter handler
addFilterEventListener(({ field, value, operator }) => {
rootDm.propagate(
[{
criteria: {
field: field,
value: value,
operator: Datamodel.ComparisonOperators[operator]
},
fields: [field]
}],
{
payload: { action: "filter" }
}
);
});
// Add reset handler
addResetEventListener(() => {
g1.render(groupedOrigin);
g2.render(groupedMaker);
});
Example Results
Propagation is particularly powerful when:
- Building interactive dashboards
- Creating linked visualizations
- Implementing cross-filtering functionality
- Maintaining data consistency across multiple views