16224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala/* 26224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Copyright 2013 AndroidPlot.com 36224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * 46224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Licensed under the Apache License, Version 2.0 (the "License"); 56224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * you may not use this file except in compliance with the License. 66224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * You may obtain a copy of the License at 76224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * 86224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * http://www.apache.org/licenses/LICENSE-2.0 96224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * 106224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Unless required by applicable law or agreed to in writing, software 116224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * distributed under the License is distributed on an "AS IS" BASIS, 126224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 136224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * See the License for the specific language governing permissions and 146224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * limitations under the License. 156224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala */ 166224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 176224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalapackage com.androidplot.pie; 186224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 196224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport android.graphics.*; 206224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 216224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport com.androidplot.exception.PlotRenderException; 226224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport com.androidplot.ui.SeriesRenderer; 236224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 246224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport java.util.Set; 256224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 266224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalapublic class PieRenderer extends SeriesRenderer<PieChart, Segment, SegmentFormatter> { 276224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 286224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // starting angle to use when drawing the first radial line of the first segment. 296224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala @SuppressWarnings("FieldCanBeLocal") 306224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala private float startDeg = 0; 316224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala private float endDeg = 360; 326224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 336224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // TODO: express donut in units other than px. 346224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala private float donutSize = 0.5f; 356224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala private DonutMode donutMode = DonutMode.PERCENT; 366224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 376224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala public enum DonutMode { 386224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala PERCENT, 396224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala DP, 406224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala PIXELS 416224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 426224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 436224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala public PieRenderer(PieChart plot) { 446224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala super(plot); 456224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 466224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 476224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala public float getRadius(RectF rect) { 486224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala return rect.width() < rect.height() ? rect.width() / 2 : rect.height() / 2; 496224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 506224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 516224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala @Override 526224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala public void onRender(Canvas canvas, RectF plotArea) throws PlotRenderException { 536224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 546224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala float radius = getRadius(plotArea); 556224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala PointF origin = new PointF(plotArea.centerX(), plotArea.centerY()); 566224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 576224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala double[] values = getValues(); 586224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala double scale = calculateScale(values); 596224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala float offset = startDeg; 606224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala Set<Segment> segments = getPlot().getSeriesSet(); 616224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 626224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala //PointF lastRadial = calculateLineEnd(origin, radius, offset); 636224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 646224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala RectF rec = new RectF(origin.x - radius, origin.y - radius, origin.x + radius, origin.y + radius); 656224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 666224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala int i = 0; 676224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala for (Segment segment : segments) { 686224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala float lastOffset = offset; 696224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala float sweep = (float) (scale * (values[i]) * 360); 706224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala offset += sweep; 716224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala //PointF radial = calculateLineEnd(origin, radius, offset); 726224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala drawSegment(canvas, rec, segment, getPlot().getFormatter(segment, PieRenderer.class), 736224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala radius, lastOffset, sweep); 746224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala //lastRadial = radial; 756224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala i++; 766224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 776224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 786224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 796224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala protected void drawSegment(Canvas canvas, RectF bounds, Segment seg, SegmentFormatter f, 806224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala float rad, float startAngle, float sweep) { 816224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala canvas.save(); 826224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 836224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala float cx = bounds.centerX(); 846224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala float cy = bounds.centerY(); 856224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 866224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala float donutSizePx; 876224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala switch(donutMode) { 886224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala case PERCENT: 896224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala donutSizePx = donutSize * rad; 906224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala break; 916224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala case PIXELS: 926224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala donutSizePx = (donutSize > 0)?donutSize:(rad + donutSize); 936224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala break; 946224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala default: 956224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala throw new UnsupportedOperationException("Not yet implemented."); 966224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 976224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 986224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // vertices of the first radial: 996224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala PointF r1Outer = calculateLineEnd(cx, cy, rad, startAngle); 1006224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala PointF r1Inner = calculateLineEnd(cx, cy, donutSizePx, startAngle); 1016224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1026224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // vertices of the second radial: 1036224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala PointF r2Outer = calculateLineEnd(cx, cy, rad, startAngle + sweep); 1046224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala PointF r2Inner = calculateLineEnd(cx, cy, donutSizePx, startAngle + sweep); 1056224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1066224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala Path clip = new Path(); 1076224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1086224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala //float outerStroke = f.getOuterEdgePaint().getStrokeWidth(); 1096224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala //float halfOuterStroke = outerStroke / 2; 1106224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1116224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // leave plenty of room on the outside for stroked borders; 1126224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // necessary because the clipping border is ugly 1136224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // and cannot be easily anti aliased. Really we only care about masking off the 1146224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // radial edges. 1156224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala clip.arcTo(new RectF(bounds.left - rad, 1166224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala bounds.top - rad, 1176224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala bounds.right + rad, 1186224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala bounds.bottom + rad), 1196224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala startAngle, sweep); 1206224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala clip.lineTo(cx, cy); 1216224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala clip.close(); 1226224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala canvas.clipPath(clip); 1236224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1246224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala Path p = new Path(); 1256224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala p.arcTo(bounds, startAngle, sweep); 1266224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala p.lineTo(r2Inner.x, r2Inner.y); 1276224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1286224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // sweep back to original angle: 1296224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala p.arcTo(new RectF( 1306224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala cx - donutSizePx, 1316224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala cy - donutSizePx, 1326224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala cx + donutSizePx, 1336224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala cy + donutSizePx), 1346224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala startAngle + sweep, -sweep); 1356224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1366224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala p.close(); 1376224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1386224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // fill segment: 1396224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala canvas.drawPath(p, f.getFillPaint()); 1406224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1416224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // draw radial lines 1426224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala canvas.drawLine(r1Inner.x, r1Inner.y, r1Outer.x, r1Outer.y, f.getRadialEdgePaint()); 1436224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala canvas.drawLine(r2Inner.x, r2Inner.y, r2Outer.x, r2Outer.y, f.getRadialEdgePaint()); 1446224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1456224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // draw inner line: 1466224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala canvas.drawCircle(cx, cy, donutSizePx, f.getInnerEdgePaint()); 1476224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1486224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // draw outer line: 1496224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala canvas.drawCircle(cx, cy, rad, f.getOuterEdgePaint()); 1506224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala canvas.restore(); 1516224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1526224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala PointF labelOrigin = calculateLineEnd(cx, cy, 1536224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala (rad-((rad- donutSizePx)/2)), startAngle + (sweep/2)); 1546224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1556224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // TODO: move segment labelling outside the segment drawing loop 1566224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // TODO: so that the labels will not be clipped by the edge of the next 1576224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // TODO: segment being drawn. 1586224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala drawSegmentLabel(canvas, labelOrigin, seg, f); 1596224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 1606224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1616224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala protected void drawSegmentLabel(Canvas canvas, PointF origin, 1626224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala Segment seg, SegmentFormatter f) { 1636224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala canvas.drawText(seg.getTitle(), origin.x, origin.y, f.getLabelPaint()); 1646224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1656224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 1666224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1676224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala @Override 1686224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala protected void doDrawLegendIcon(Canvas canvas, RectF rect, SegmentFormatter formatter) { 1696224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala throw new UnsupportedOperationException("Not yet implemented."); 1706224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 1716224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1726224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala /** 1736224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Determines how many counts there are per cent of whatever the 1746224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * pie chart is displaying as a fraction, 1 being 100%. 1756224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala */ 1766224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala private double calculateScale(double[] values) { 1776224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala double total = 0; 1786224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala for (int i = 0; i < values.length; i++) { 1796224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala total += values[i]; 1806224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 1816224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1826224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala return (1d / total); 1836224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 1846224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1856224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala private double[] getValues() { 1866224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala Set<Segment> segments = getPlot().getSeriesSet(); 1876224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala double[] result = new double[segments.size()]; 1886224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala int i = 0; 1896224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala for (Segment seg : getPlot().getSeriesSet()) { 1906224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala result[i] = seg.getValue().doubleValue(); 1916224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala i++; 1926224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 1936224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala return result; 1946224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 1956224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 1966224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala private PointF calculateLineEnd(float x, float y, float rad, float deg) { 1976224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala return calculateLineEnd(new PointF(x, y), rad, deg); 1986224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 1996224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 2006224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala private PointF calculateLineEnd(PointF origin, float rad, float deg) { 2016224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 2026224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala double radians = deg * Math.PI / 180F; 2036224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala double x = rad * Math.cos(radians); 2046224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala double y = rad * Math.sin(radians); 2056224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 2066224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala // convert to screen space: 2076224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala return new PointF(origin.x + (float) x, origin.y + (float) y); 2086224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 2096224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 2106224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala public void setDonutSize(float size, DonutMode mode) { 2116224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala switch(mode) { 2126224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala case PERCENT: 2136224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala if(size < 0 || size > 1) { 2146224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala throw new IllegalArgumentException( 2156224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala "Size parameter must be between 0 and 1 when operating in PERCENT mode."); 2166224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 2176224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala break; 2186224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala case PIXELS: 2196224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala break; 2206224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala default: 2216224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala throw new UnsupportedOperationException("Not yet implemented."); 2226224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 2236224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala donutMode = mode; 2246224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala donutSize = size; 2256224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 2266224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 2276224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala public void setStartDeg(float deg) { 2286224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala startDeg = deg; 2296224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 2306224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala 2316224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala public void setEndDeg(float deg) { 2326224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala endDeg = deg; 2336224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala } 2346224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala} 235