diff --git a/examples/Demoanlage/README.md b/examples/Demoanlage/README.md
index 7af8a1b..299bee8 100644
--- a/examples/Demoanlage/README.md
+++ b/examples/Demoanlage/README.md
@@ -6,8 +6,11 @@ Die Demo erzeugt idempotent eine vollständige, spielbare EMS-Anlage in IP-Symco
- zwei mehrstufige Boiler (1,2/2,4 kW und 1,5/3,0 kW),
- einen einstufigen Entfeuchter (0,9 kW),
- simulierte PV-Erzeugung, Grundlast, Netzleistung und Boilertemperaturen,
-- Kachelvisualisierung mit Energiefluss und bedienbaren Simulationswerten,
-- Archivierung der wichtigsten Leistungs- und Temperaturwerte.
+- native Symcon-Instanz `Energy Distribution` für den Energiefluss,
+- eigene Kachelansichten mit allen Laufzeitparametern je Boiler,
+- kombinierten Verlauf für PV-Leistung, Hausverbrauch und Netzleistung,
+- kombinierten Verlauf der kumulierten PV-, Haus-, Bezugs- und Einspeiseenergie,
+- Archivierung der Leistungs-, Energie- und Temperaturwerte.
## Installation
@@ -31,12 +34,20 @@ Unter `Simulation` lassen sich Tageslauf, Tageszeit, Geschwindigkeit,
PV-Spitzenleistung, Bewölkung und Haus-Grundlast verändern. Der Manager verteilt
den verfügbaren PV-Überschuss nach Priorität auf beide Boiler und den Entfeuchter.
+`Übersicht` zeigt die native Symcon-Energieverteilung. Die Kacheln `Boiler 1`
+und `Boiler 2` öffnen direkt sämtliche Bedien-, Status- und Diagnosevariablen,
+ohne zuerst das Instanz-Konfigurationsformular zu zeigen. Unter `Leistung und
+Energie` liegen die beiden kombinierten Zeitdiagramme und die Energiezähler.
+Die Energiesummen verwenden die beschleunigte Simulationszeit.
+
## Validierung
- Alle vier Enelix-Instanzen müssen Status 102 besitzen.
- Der Manager muss drei Verbraucher melden.
- Bei ausreichendem PV-Überschuss müssen Verbraucher schalten.
-- Netzleistung, PV-Leistung, Verbraucherleistung und Temperaturen werden archiviert.
+- Netzleistung, PV-Leistung, Hausverbrauch, Verbraucherleistungen, Energiesummen und Temperaturen werden archiviert.
+- Die native Energieverteilung muss sechs Knoten enthalten.
+- Beide Diagrammkacheln müssen mindestens drei beschriftete Zeitreihen anzeigen.
- Ein erneuter Installationslauf muss dieselbe Root-ID zurückgeben.
## Rollback
diff --git a/examples/Demoanlage/bootstrap.php b/examples/Demoanlage/bootstrap.php
index 3e9703d..5c13909 100644
--- a/examples/Demoanlage/bootstrap.php
+++ b/examples/Demoanlage/bootstrap.php
@@ -8,6 +8,7 @@ const ENELIX_DEMO_BOILER_MODULE = '{B7C54AF4-AD7D-4FE4-B75D-203693906251}';
const ENELIX_DEMO_SINGLE_MODULE = '{15879A4E-D0C2-4495-83DE-46E1E462591E}';
const ENELIX_DEMO_TILE_MODULE = '{B5B875BB-9B76-45FD-4E67-2607E45B3AC4}';
const ENELIX_DEMO_ARCHIVE_MODULE = '{43192F0B-135B-4CE7-A0A7-1475603F3060}';
+const ENELIX_DEMO_ENERGY_DISTRIBUTION_MODULE = '{BA90FDB3-C203-8F26-B4D6-F3DB99905724}';
function enelixDemoInstall(): array
{
@@ -16,17 +17,32 @@ function enelixDemoInstall(): array
$root = enelixDemoEnsureCategory(0, ENELIX_DEMO_ROOT_IDENT, 'Enelix 2 Demoanlage', 100);
IPS_SetInfo($root, 'Idempotent erzeugte Enelix-2-Demo. Entfernen nur mit enelixDemoRemove(true).');
- $dashboard = enelixDemoEnsureCategory($root, 'Dashboard', 'Energiefluss', 10);
+ $dashboard = enelixDemoEnsureCategory($root, 'Dashboard', 'Übersicht', 10);
$simulation = enelixDemoEnsureCategory($root, 'Simulation', 'Simulation', 20);
- $ems = enelixDemoEnsureCategory($root, 'EMS', 'Manager und Verbraucher', 30);
+ $boiler1View = enelixDemoEnsureCategory($root, 'Boiler1Ansicht', 'Boiler 1', 30);
+ $boiler2View = enelixDemoEnsureCategory($root, 'Boiler2Ansicht', 'Boiler 2', 40);
+ $history = enelixDemoEnsureCategory($root, 'Historie', 'Leistung und Energie', 50);
+ $ems = enelixDemoEnsureCategory($root, 'EMS', 'Manager und Verbraucher', 60);
$technical = enelixDemoEnsureCategory($root, 'Technik', 'Simulationstechnik', 90, true);
enelixDemoEnsureProfiles();
- $flow = enelixDemoEnsureVariable($dashboard, 'Energiefluss', 'Energiefluss', 3, '~HTMLBox', '', 10);
+ $legacyFlow = enelixDemoEnsureVariable($dashboard, 'Energiefluss', 'Energiefluss (Legacy)', 3, '~HTMLBox', '', 90);
+ IPS_SetHidden($legacyFlow, true);
$pvDisplay = enelixDemoEnsureVariable($dashboard, 'PVLeistung', 'PV-Leistung', 2, 'ENELIX.Demo.PowerPositive', 0.0, 20);
- $loadDisplay = enelixDemoEnsureVariable($dashboard, 'Verbraucherleistung', 'Geregelte Verbraucher', 2, 'ENELIX.Demo.PowerPositive', 0.0, 30);
- $grid = enelixDemoEnsureVariable($dashboard, 'Netzleistung', 'Netzleistung', 2, 'ENELIX.Demo.Power', 0.0, 40);
+ $houseDisplay = enelixDemoEnsureVariable($dashboard, 'Hausverbrauch', 'Hausverbrauch gesamt', 2, 'ENELIX.Demo.PowerPositive', 0.0, 30);
+ $loadDisplay = enelixDemoEnsureVariable($dashboard, 'Verbraucherleistung', 'Geregelte Verbraucher', 2, 'ENELIX.Demo.PowerPositive', 0.0, 40);
+ $grid = enelixDemoEnsureVariable($dashboard, 'Netzleistung', 'Netzleistung', 2, 'ENELIX.Demo.Power', 0.0, 50);
+ $boiler1Power = enelixDemoEnsureVariable($dashboard, 'Boiler1Leistung', 'Boiler 1 Leistung', 2, 'ENELIX.Demo.PowerPositive', 0.0, 60);
+ $boiler2Power = enelixDemoEnsureVariable($dashboard, 'Boiler2Leistung', 'Boiler 2 Leistung', 2, 'ENELIX.Demo.PowerPositive', 0.0, 70);
+ $dryerPower = enelixDemoEnsureVariable($dashboard, 'EntfeuchterLeistung', 'Entfeuchter Leistung', 2, 'ENELIX.Demo.PowerPositive', 0.0, 80);
+
+ $powerChart = enelixDemoEnsureVariable($history, 'Leistungsdiagramm', 'Leistungsverlauf', 3, '~HTMLBox', '', 10);
+ $energyChart = enelixDemoEnsureVariable($history, 'Energiediagramm', 'Kumulierte Energien', 3, '~HTMLBox', '', 20);
+ $pvEnergy = enelixDemoEnsureVariable($history, 'PVEnergie', 'PV-Energie', 2, 'ENELIX.Demo.Energy', 0.0, 30);
+ $houseEnergy = enelixDemoEnsureVariable($history, 'HausEnergie', 'Hausverbrauch Energie', 2, 'ENELIX.Demo.Energy', 0.0, 40);
+ $gridImportEnergy = enelixDemoEnsureVariable($history, 'NetzbezugEnergie', 'Netzbezug Energie', 2, 'ENELIX.Demo.Energy', 0.0, 50);
+ $gridExportEnergy = enelixDemoEnsureVariable($history, 'EinspeisungEnergie', 'Einspeisung Energie', 2, 'ENELIX.Demo.Energy', 0.0, 60);
$auto = enelixDemoEnsureVariable($simulation, 'Automatik', 'Tageslauf automatisch', 0, '~Switch', true, 10);
$hour = enelixDemoEnsureVariable($simulation, 'Tageszeit', 'Simulierte Tageszeit', 2, 'ENELIX.Demo.Hour', 8.0, 20);
@@ -70,6 +86,8 @@ function enelixDemoInstall(): array
$boiler21 = enelixDemoEnsureVariable($technical, 'Boiler2Stufe1', 'Boiler 2 Stufe 1', 0, '~Switch', false, 140);
$boiler22 = enelixDemoEnsureVariable($technical, 'Boiler2Stufe2', 'Boiler 2 Stufe 2', 0, '~Switch', false, 150);
$dryContact = enelixDemoEnsureVariable($technical, 'EntfeuchterKontakt', 'Entfeuchter Kontakt', 0, '~Switch', false, 160);
+ $chartUpdated = enelixDemoEnsureVariable($technical, 'LetztesDiagrammUpdate', 'Letztes Diagramm-Update', 1, '', 0, 170);
+ IPS_SetHidden($chartUpdated, true);
foreach ([$boiler11, $boiler12, $boiler21, $boiler22, $dryContact] as $actorId) {
IPS_SetVariableCustomAction($actorId, $actorScript);
@@ -128,6 +146,22 @@ function enelixDemoInstall(): array
'DiagnosevariablenAnzeigen' => true,
]);
+ $energyDistribution = enelixDemoEnsureInstance(
+ $dashboard,
+ 'Energieverteilung',
+ 'Energieverteilung',
+ ENELIX_DEMO_ENERGY_DISTRIBUTION_MODULE,
+ 10
+ );
+ enelixDemoConfigureEnergyDistribution($energyDistribution, [
+ enelixDemoEnergyNode($pvDisplay, 'PV-Anlage', 'solar'),
+ enelixDemoEnergyNode($grid, 'Stromnetz', 'grid'),
+ enelixDemoEnergyNode($base, 'Haus-Grundlast', 'consumer'),
+ enelixDemoEnergyNode($boiler1Power, 'Boiler 1', 'consumer', $temp1),
+ enelixDemoEnergyNode($boiler2Power, 'Boiler 2', 'consumer', $temp2),
+ enelixDemoEnergyNode($dryerPower, 'Entfeuchter', 'consumer'),
+ ]);
+
foreach ([$boiler1, $boiler2] as $boilerId) {
IPS_RequestAction($boilerId, 'Mindesttemperatur', 42.0);
IPS_RequestAction($boilerId, 'Maximaltemperatur', 62.0);
@@ -160,15 +194,25 @@ function enelixDemoInstall(): array
}
IPS_RequestAction($manager, 'Aktiv', true);
- enelixDemoEnsureLink($dashboard, 'ManagerLink', 'Manager', $manager, 50);
- enelixDemoEnsureLink($dashboard, 'Boiler1Link', 'Boiler 1', $boiler1, 60);
- enelixDemoEnsureLink($dashboard, 'Boiler2Link', 'Boiler 2', $boiler2, 70);
- enelixDemoEnsureLink($dashboard, 'EntfeuchterLink', 'Entfeuchter', $dryer, 80);
+ enelixDemoLinkInstanceVariables($boiler1View, $boiler1, $temp1);
+ enelixDemoLinkInstanceVariables($boiler2View, $boiler2, $temp2);
+ IPS_SetHidden($boiler1, true);
+ IPS_SetHidden($boiler2, true);
+
+ enelixDemoEnsureLink($dashboard, 'ManagerLink', 'Manager', $manager, 100);
+ enelixDemoEnsureLink($dashboard, 'Boiler1Link', 'Boiler 1', $boiler1View, 110);
+ enelixDemoEnsureLink($dashboard, 'Boiler2Link', 'Boiler 2', $boiler2View, 120);
+ enelixDemoEnsureLink($dashboard, 'EntfeuchterLink', 'Entfeuchter', $dryer, 130);
enelixDemoEnsureLink($simulation, 'Boiler1TemperaturLink', 'Boiler 1 Temperatur', $temp1, 70);
enelixDemoEnsureLink($simulation, 'Boiler2TemperaturLink', 'Boiler 2 Temperatur', $temp2, 80);
- enelixDemoConfigureArchive([$pvDisplay, $loadDisplay, $grid, $temp1, $temp2]);
- enelixDemoConfigureVisualization($root, $flow);
+ enelixDemoConfigureArchive([
+ $pvDisplay, $houseDisplay, $loadDisplay, $grid,
+ $boiler1Power, $boiler2Power, $dryerPower,
+ $pvEnergy, $houseEnergy, $gridImportEnergy, $gridExportEnergy,
+ $temp1, $temp2,
+ ]);
+ enelixDemoConfigureVisualization($root, $energyDistribution);
enelixDemoTick('Install');
return [
@@ -234,25 +278,44 @@ function enelixDemoTick(string $source = 'Manual'): void
$manager = enelixDemoRequiredId($ems, 'Manager');
IPS_RequestAction($manager, 'Regeln', true);
- $power = enelixDemoControlledPower($contacts);
- $grid = $base + $power - $pv;
- SetValue($gridId, round($grid, 1));
- SetValue(enelixDemoRequiredId($dashboard, 'PVLeistung'), round($pv, 1));
- SetValue(enelixDemoRequiredId($dashboard, 'Verbraucherleistung'), (float) $power);
+ $boiler1 = (bool) GetValue($contacts['boiler12']) ? 2400 : ((bool) GetValue($contacts['boiler11']) ? 1200 : 0);
+ $boiler2 = (bool) GetValue($contacts['boiler22']) ? 3000 : ((bool) GetValue($contacts['boiler21']) ? 1500 : 0);
+ $dryer = (bool) GetValue($contacts['dryer']) ? 900 : 0;
+ $power = $boiler1 + $boiler2 + $dryer;
+ $house = $base + $power;
+ $grid = $house - $pv;
- $state = [
- 'hour' => $hour,
- 'pv' => $pv,
- 'base' => $base,
- 'grid' => $grid,
- 'boiler1' => (bool) GetValue($contacts['boiler12']) ? 2400 : ((bool) GetValue($contacts['boiler11']) ? 1200 : 0),
- 'boiler2' => (bool) GetValue($contacts['boiler22']) ? 3000 : ((bool) GetValue($contacts['boiler21']) ? 1500 : 0),
- 'dryer' => (bool) GetValue($contacts['dryer']) ? 900 : 0,
- 'temp1' => (float) GetValue($temp1),
- 'temp2' => (float) GetValue($temp2),
- 'manager' => (string) GetValue(enelixDemoRequiredId($manager, 'Betriebsart')),
+ $pvId = enelixDemoRequiredId($dashboard, 'PVLeistung');
+ $houseId = enelixDemoRequiredId($dashboard, 'Hausverbrauch');
+ $loadId = enelixDemoRequiredId($dashboard, 'Verbraucherleistung');
+ SetValue($gridId, round($grid, 1));
+ SetValue($pvId, round($pv, 1));
+ SetValue($houseId, round($house, 1));
+ SetValue($loadId, (float) $power);
+ SetValue(enelixDemoRequiredId($dashboard, 'Boiler1Leistung'), (float) $boiler1);
+ SetValue(enelixDemoRequiredId($dashboard, 'Boiler2Leistung'), (float) $boiler2);
+ SetValue(enelixDemoRequiredId($dashboard, 'EntfeuchterLeistung'), (float) $dryer);
+
+ $history = enelixDemoRequiredId($root, 'Historie');
+ $energyIds = [
+ 'pv' => enelixDemoRequiredId($history, 'PVEnergie'),
+ 'house' => enelixDemoRequiredId($history, 'HausEnergie'),
+ 'import' => enelixDemoRequiredId($history, 'NetzbezugEnergie'),
+ 'export' => enelixDemoRequiredId($history, 'EinspeisungEnergie'),
];
- SetValue(enelixDemoRequiredId($dashboard, 'Energiefluss'), enelixDemoRenderFlow($state));
+ if ($source === 'Timer') {
+ $hours = $minutes / 60.0;
+ enelixDemoAddEnergy($energyIds['pv'], $pv, $hours);
+ enelixDemoAddEnergy($energyIds['house'], $house, $hours);
+ enelixDemoAddEnergy($energyIds['import'], max(0.0, $grid), $hours);
+ enelixDemoAddEnergy($energyIds['export'], max(0.0, -$grid), $hours);
+ }
+
+ $chartUpdated = enelixDemoRequiredId($technical, 'LetztesDiagrammUpdate');
+ if ($source !== 'Timer' || time() - (int) GetValue($chartUpdated) >= 10) {
+ enelixDemoUpdateCharts($history, [$pvId, $houseId, $gridId], $energyIds);
+ SetValue($chartUpdated, time());
+ }
}
function enelixDemoRenderFlow(array $state): string
@@ -309,7 +372,12 @@ function enelixDemoValidateRuntime(): void
throw new RuntimeException('Die Demo benötigt IP-Symcon 8.x; aktiv ist ' . $version . '.');
}
$available = IPS_GetModuleList();
- foreach ([ENELIX_DEMO_MANAGER_MODULE, ENELIX_DEMO_BOILER_MODULE, ENELIX_DEMO_SINGLE_MODULE] as $moduleId) {
+ foreach ([
+ ENELIX_DEMO_MANAGER_MODULE,
+ ENELIX_DEMO_BOILER_MODULE,
+ ENELIX_DEMO_SINGLE_MODULE,
+ ENELIX_DEMO_ENERGY_DISTRIBUTION_MODULE,
+ ] as $moduleId) {
if (!in_array($moduleId, $available, true)) {
throw new RuntimeException('Benötigtes Enelix-Modul fehlt: ' . $moduleId);
}
@@ -323,6 +391,7 @@ function enelixDemoEnsureProfiles(): void
enelixDemoEnsureProfile('ENELIX.Demo.Hour', 2, 0, 23.75, 0.25, 2, ' h');
enelixDemoEnsureProfile('ENELIX.Demo.Percent', 1, 0, 100, 5, 0, ' %');
enelixDemoEnsureProfile('ENELIX.Demo.Speed', 1, 1, 60, 1, 0, ' min');
+ enelixDemoEnsureProfile('ENELIX.Demo.Energy', 2, 0, 1000000, 0.01, 2, ' kWh');
}
function enelixDemoEnsureProfile(string $name, int $type, float $min, float $max, float $step, int $digits, string $suffix): void
@@ -463,6 +532,7 @@ function enelixDemoConfigureArchive(array $variableIds): void
foreach ($variableIds as $variableId) {
AC_SetLoggingStatus($archive, $variableId, true);
AC_SetAggregationType($archive, $variableId, 0);
+ AC_SetGraphStatus($archive, $variableId, true);
}
IPS_ApplyChanges($archive);
}
@@ -526,7 +596,7 @@ function enelixDemoRemove(bool $confirmed = false): void
IPS_ApplyChanges($visualization);
}
enelixDemoDeleteTree($root);
- foreach (['ENELIX.Demo.Power', 'ENELIX.Demo.PowerPositive', 'ENELIX.Demo.Hour', 'ENELIX.Demo.Percent', 'ENELIX.Demo.Speed'] as $profile) {
+ foreach (['ENELIX.Demo.Power', 'ENELIX.Demo.PowerPositive', 'ENELIX.Demo.Hour', 'ENELIX.Demo.Percent', 'ENELIX.Demo.Speed', 'ENELIX.Demo.Energy'] as $profile) {
if (IPS_VariableProfileExists($profile)) {
IPS_DeleteVariableProfile($profile);
}
@@ -553,3 +623,174 @@ function enelixDemoDeleteTree(int $objectId): void
default: throw new RuntimeException('Unbekannter Objekttyp: ' . $type);
}
}
+
+
+function enelixDemoEnergyNode(
+ int $variableId,
+ string $name,
+ string $type,
+ int $infoVariableId = 0
+): array {
+ return [
+ 'VariableConfigType' => 0,
+ 'VariableID' => $variableId,
+ 'SecondaryVariableID' => 0,
+ 'Name' => $name,
+ 'Type' => $type,
+ 'Grouped' => false,
+ 'CalculationType' => 0,
+ 'InfoVariableID' => $infoVariableId,
+ 'Invert' => false,
+ ];
+}
+
+function enelixDemoConfigureEnergyDistribution(int $instanceId, array $nodes): void
+{
+ enelixDemoConfigure($instanceId, [
+ 'Calculation' => false,
+ 'Config' => json_encode($nodes, JSON_THROW_ON_ERROR),
+ 'EnableLimits' => false,
+ 'Restrictions' => '[]',
+ ]);
+}
+
+function enelixDemoLinkInstanceVariables(int $categoryId, int $instanceId, int $sensorId): void
+{
+ enelixDemoEnsureLink($categoryId, 'SensorTemperatur', 'Gemessene Temperatur', $sensorId, 5);
+ $position = 10;
+ foreach (IPS_GetChildrenIDs($instanceId) as $childId) {
+ if ((int) IPS_GetObject($childId)['ObjectType'] !== 2) {
+ continue;
+ }
+ $object = IPS_GetObject($childId);
+ $ident = $object['ObjectIdent'] !== '' ? $object['ObjectIdent'] : (string) $childId;
+ enelixDemoEnsureLink(
+ $categoryId,
+ 'Wert_' . preg_replace('/[^A-Za-z0-9_]/', '_', $ident),
+ (string) $object['ObjectName'],
+ (int) $childId,
+ $position
+ );
+ $position += 10;
+ }
+}
+
+function enelixDemoAddEnergy(int $variableId, float $powerW, float $hours): void
+{
+ $energy = (float) GetValue($variableId) + (($powerW * $hours) / 1000.0);
+ SetValue($variableId, round($energy, 4));
+}
+
+function enelixDemoUpdateCharts(int $historyId, array $powerIds, array $energyIds): void
+{
+ $archive = enelixDemoFirstInstance(ENELIX_DEMO_ARCHIVE_MODULE);
+ if ($archive === 0) {
+ return;
+ }
+ $end = time();
+ $start = $end - 1200;
+ $powerSeries = [
+ ['name' => 'PV', 'color' => '#f4c542', 'values' => enelixDemoLoggedSeries($archive, $powerIds[0], $start, $end)],
+ ['name' => 'Hausverbrauch', 'color' => '#2bb673', 'values' => enelixDemoLoggedSeries($archive, $powerIds[1], $start, $end)],
+ ['name' => 'Netzleistung', 'color' => '#e95d5d', 'values' => enelixDemoLoggedSeries($archive, $powerIds[2], $start, $end)],
+ ];
+ $energySeries = [
+ ['name' => 'PV', 'color' => '#f4c542', 'values' => enelixDemoLoggedSeries($archive, $energyIds['pv'], $start, $end)],
+ ['name' => 'Haus', 'color' => '#2bb673', 'values' => enelixDemoLoggedSeries($archive, $energyIds['house'], $start, $end)],
+ ['name' => 'Netzbezug', 'color' => '#e95d5d', 'values' => enelixDemoLoggedSeries($archive, $energyIds['import'], $start, $end)],
+ ['name' => 'Einspeisung', 'color' => '#38a3db', 'values' => enelixDemoLoggedSeries($archive, $energyIds['export'], $start, $end)],
+ ];
+ SetValue(
+ enelixDemoRequiredId($historyId, 'Leistungsdiagramm'),
+ enelixDemoRenderChart('Leistungsverlauf · letzte 20 Minuten', $powerSeries, 'W', $start, $end)
+ );
+ SetValue(
+ enelixDemoRequiredId($historyId, 'Energiediagramm'),
+ enelixDemoRenderChart('Kumulierte Energie · Simulationszeit', $energySeries, 'kWh', $start, $end)
+ );
+}
+
+function enelixDemoLoggedSeries(int $archiveId, int $variableId, int $start, int $end): array
+{
+ $values = array_reverse(AC_GetLoggedValues($archiveId, $variableId, $start, $end, 0));
+ if ($values === []) {
+ return [['time' => $end, 'value' => (float) GetValue($variableId)]];
+ }
+ $step = max(1, (int) ceil(count($values) / 90));
+ $result = [];
+ foreach ($values as $index => $value) {
+ if ($index % $step !== 0 && $index !== count($values) - 1) {
+ continue;
+ }
+ $result[] = [
+ 'time' => (int) $value['TimeStamp'],
+ 'value' => (float) $value['Value'],
+ ];
+ }
+ return $result;
+}
+
+function enelixDemoRenderChart(
+ string $title,
+ array $series,
+ string $unit,
+ int $start,
+ int $end
+): string {
+ $allValues = [0.0];
+ foreach ($series as $line) {
+ foreach ($line['values'] as $point) {
+ $allValues[] = (float) $point['value'];
+ }
+ }
+ $min = min($allValues);
+ $max = max($allValues);
+ if (abs($max - $min) < 0.0001) {
+ $max = $min + 1.0;
+ }
+
+ $left = 72.0;
+ $top = 34.0;
+ $width = 650.0;
+ $height = 210.0;
+ $timeRange = max(1, $end - $start);
+ $valueRange = $max - $min;
+ $grid = '';
+ for ($index = 0; $index <= 4; $index++) {
+ $y = $top + (($height / 4) * $index);
+ $value = $max - (($valueRange / 4) * $index);
+ $grid .= '