Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(#135): sort boxes by parent first #138

Merged
merged 2 commits into from
Oct 18, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 101 additions & 96 deletions src/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ export class Chart extends LitElement {
if (!this.config) {
return false;
}
if (changedProps.has('config') || changedProps.has('forceUpdateTs') || changedProps.has('highlightedEntities') || changedProps.has('zoomEntity')) {
if (
changedProps.has('config') ||
changedProps.has('forceUpdateTs') ||
changedProps.has('highlightedEntities') ||
changedProps.has('zoomEntity')
) {
return true;
}
const now = Date.now();
Expand Down Expand Up @@ -222,111 +227,111 @@ export class Chart extends LitElement {
private _calcBoxes() {
this.statePerPixelY = 0;
const filteredConfig = filterConfigByZoomEntity(this.config, this.zoomEntity);
this.sections = filteredConfig.sections
.map(section => {
let total = 0;
const boxes: Box[] = section.entities
.filter(entityConf => {
const { min_state } = this.config;
// remove empty entity boxes
if (entityConf.type === 'remaining_parent_state') {
return this.connectionsByChild.get(entityConf)?.some(c => c.state && c.state >= min_state);
}
if (entityConf.type === 'remaining_child_state') {
return this.connectionsByParent.get(entityConf)?.some(c => c.state && c.state >= min_state);
}
const { state } = this._getMemoizedState(entityConf);
return state && state >= min_state;
})
.map(entityConf => {
const { state, unit_of_measurement } = this._getMemoizedState(entityConf);
total += state;

let finalColor = entityConf.color || 'var(--primary-color)';
if (typeof entityConf.color_on_state != 'undefined' && entityConf.color_on_state) {
const colorLimit = typeof entityConf.color_limit === 'undefined' ? 1 : entityConf.color_limit;
const colorBelow =
typeof entityConf.color_below === 'undefined' ? 'var(--primary-color)' : entityConf.color_below;
const colorAbove =
typeof entityConf.color_above === 'undefined' ? 'var(--paper-item-icon-color)' : entityConf.color_above;
finalColor = state > colorLimit ? colorAbove : colorBelow;
}
this.sections = [];
filteredConfig.sections.forEach(section => {
let total = 0;
const boxes: Box[] = section.entities
.filter(entityConf => {
const { min_state } = this.config;
// remove empty entity boxes
if (entityConf.type === 'remaining_parent_state') {
return this.connectionsByChild.get(entityConf)?.some(c => c.state && c.state >= min_state);
}
if (entityConf.type === 'remaining_child_state') {
return this.connectionsByParent.get(entityConf)?.some(c => c.state && c.state >= min_state);
}
const { state } = this._getMemoizedState(entityConf);
return state && state >= min_state;
})
.map(entityConf => {
const { state, unit_of_measurement } = this._getMemoizedState(entityConf);
total += state;

let finalColor = entityConf.color || 'var(--primary-color)';
if (typeof entityConf.color_on_state != 'undefined' && entityConf.color_on_state) {
const colorLimit = typeof entityConf.color_limit === 'undefined' ? 1 : entityConf.color_limit;
const colorBelow =
typeof entityConf.color_below === 'undefined' ? 'var(--primary-color)' : entityConf.color_below;
const colorAbove =
typeof entityConf.color_above === 'undefined' ? 'var(--paper-item-icon-color)' : entityConf.color_above;
finalColor = state > colorLimit ? colorAbove : colorBelow;
}

return {
config: entityConf,
entity: this._getEntityState(entityConf),
entity_id: getEntityId(entityConf),
state,
unit_of_measurement,
color: finalColor,
children: entityConf.children,
connections: { parents: [] },
top: 0,
size: 0,
};
});
if (!boxes.length) {
return {
boxes,
total,
spacerH: 0,
statePerPixelY: 0,
config: entityConf,
entity: this._getEntityState(entityConf),
entity_id: getEntityId(entityConf),
state,
unit_of_measurement,
color: finalColor,
children: entityConf.children,
connections: { parents: [] },
top: 0,
size: 0,
};
}
// leave room for margin
const availableHeight = this.config.height - (boxes.length - 1) * this.config.min_box_distance;
// calc sizes to determine statePerPixelY ratio and find the best one
const calcResults = this._calcBoxHeights(boxes, availableHeight, total);
return {
boxes: this._sortBoxes(calcResults.boxes, section.sort_by, section.sort_dir),
total,
statePerPixelY: calcResults.statePerPixelY,
};
})
.filter(s => s.boxes.length > 0)
.map(section => {
// calc sizes again with the best statePerPixelY
let totalSize = 0;
let { boxes } = section;
if (section.statePerPixelY !== this.statePerPixelY) {
boxes = boxes.map(box => {
const size = Math.max(this.config.min_box_height, Math.floor(box.state / this.statePerPixelY));
totalSize += size;
return {
...box,
size,
};
});
} else {
totalSize = boxes.reduce((sum, b) => sum + b.size, 0);
}
// calc vertical margin size
const extraSpace = this.config.height - totalSize;
const spacerH = boxes.length > 1 ? extraSpace / (boxes.length - 1) : this.config.height;
let offset = 0;
// calc y positions. needed for connectors
boxes = boxes.map(box => {
const top = offset;
offset += box.size + spacerH;
});
if (!boxes.length) {
return;
}
// leave room for margin
const availableHeight = this.config.height - (boxes.length - 1) * this.config.min_box_distance;
// calc sizes to determine statePerPixelY ratio and find the best one
const calcResults = this._calcBoxHeights(boxes, availableHeight, total);
const parentBoxes = this.sections[this.sections.length - 1]?.boxes || [];
const sectionState = {
boxes: this._sortBoxes(parentBoxes, calcResults.boxes, section.sort_by, section.sort_dir),
total,
statePerPixelY: calcResults.statePerPixelY,
};

// calc sizes again with the best statePerPixelY
let totalSize = 0;
let sizedBoxes = sectionState.boxes;
if (sectionState.statePerPixelY !== this.statePerPixelY) {
sizedBoxes = sizedBoxes.map(box => {
const size = Math.max(this.config.min_box_height, Math.floor(box.state / this.statePerPixelY));
totalSize += size;
return {
...box,
top,
size,
};
});
} else {
totalSize = sizedBoxes.reduce((sum, b) => sum + b.size, 0);
}
// calc vertical margin size
const extraSpace = this.config.height - totalSize;
const spacerH = sizedBoxes.length > 1 ? extraSpace / (sizedBoxes.length - 1) : this.config.height;
let offset = 0;
// calc y positions. needed for connectors
sizedBoxes = sizedBoxes.map(box => {
const top = offset;
offset += box.size + spacerH;
return {
...section,
boxes,
spacerH,
...box,
top,
};
});
this.sections.push({
...sectionState,
boxes: sizedBoxes,
spacerH,
});
});
}

private _sortBoxes(boxes: Box[], sort?: string, dir = 'desc') {
private _sortBoxes(parentBoxes: Box[], boxes: Box[], sort?: string, dir = 'desc') {
if (sort === 'state') {
const sortByParent = (a: Box, b: Box, realSort: (a: Box, b: Box) => number) => {
const parentIndexA = parentBoxes.findIndex(p => p.children.includes(a.entity_id));
const parentIndexB = parentBoxes.findIndex(p => p.children.includes(b.entity_id));
return parentIndexA < parentIndexB ? -1 : parentIndexA > parentIndexB ? 1 : realSort(a, b);
};

if (dir === 'desc') {
boxes.sort((a, b) => (a.state > b.state ? -1 : a.state < b.state ? 1 : 0));
boxes.sort((a, b) => sortByParent(a, b, (a, b) => (a.state > b.state ? -1 : a.state < b.state ? 1 : 0)));
} else {
boxes.sort((a, b) => (a.state < b.state ? -1 : a.state > b.state ? 1 : 0));
boxes.sort((a, b) => sortByParent(a, b, (a, b) => (a.state < b.state ? -1 : a.state > b.state ? 1 : 0)));
}
}
return boxes;
Expand Down Expand Up @@ -477,7 +482,7 @@ export class Chart extends LitElement {
const maxLabelH = box.size + spacerH - 1;

// reduce label size if it doesn't fit
const labelStyle: Record<string, string> = {lineHeight: MIN_LABEL_HEIGHT + 'px'};
const labelStyle: Record<string, string> = { lineHeight: MIN_LABEL_HEIGHT + 'px' };
const nameStyle: Record<string, string> = {};
if (maxLabelH < MIN_LABEL_HEIGHT) {
const fontSize = maxLabelH / MIN_LABEL_HEIGHT;
Expand All @@ -492,11 +497,11 @@ export class Chart extends LitElement {
nameStyle.fontSize = `${1 / numLines + 0.1}rem`;
nameStyle.lineHeight = `${1 / numLines + 0.1}rem`;
} else if (maxLabelH < MIN_LABEL_HEIGHT * numLines) {
nameStyle.fontSize = `${maxLabelH / MIN_LABEL_HEIGHT / numLines * 1.1}em`;
nameStyle.lineHeight = `${maxLabelH / MIN_LABEL_HEIGHT / numLines * 1.1}em`;
nameStyle.fontSize = `${(maxLabelH / MIN_LABEL_HEIGHT / numLines) * 1.1}em`;
nameStyle.lineHeight = `${(maxLabelH / MIN_LABEL_HEIGHT / numLines) * 1.1}em`;
}
}

return html`
${i > 0 ? html`<div class="spacerv" style=${styleMap({ height: spacerH + 'px' })}></div>` : null}
${extraSpacers
Expand Down Expand Up @@ -626,4 +631,4 @@ export class Chart extends LitElement {
}
}

export default Chart;
export default Chart;