samedi 28 mars 2015

Cyfe + Mixpanel

Intro

I discovered Cyfe about one year ago and played a bit with the PushAPI widget which was very handy and easy to use.
I recently wanted to know if I could use Cyfe to display our Mixpanel metrics. And happily Cyfe has now a "widget" for Mixpanel. But sadly the widget is currently very basic and does not allow segmentation or complex requests. So I decided to implement my own "connector" with help of the existing "Private Url" widget.

Why

Why did I spent time implementing my own Mixpanel connector for Cyfe ? Here is short list of reasons:
  • One big limitation of the current Mixpanel widget is that you cannot display multiple curves in the same widget even if you specify a property to segment on.
  • Actually you can specify a property to segment on, but it will be effective only if you select 'Top unique value'. But then you will have to pick one possible value and you will see a plot of the occurrences of this value over time (one single curve).
  • There is no mean to "cleanup" raw data to avoid unwanted values like empty strings or 'undefined' values.
  • No pie chart

How

To build this connector I used the following technologies:
  1. You will find the flask rest api code on bitbucket at
    https://bitbucket.org/jams/pymixpanel
  2. Create an account on PythonAnywhere and create a project using the flask wizard.
  3. Upload the files in your flask application folder and reload.
  4. Your personal rest api should be up and ready for test.
The endpoint url should look like:
http://username.pythonanywhere.com:80/mixpanel/api/v1.0/event_name/apisecretkey/apikey?by=propertyname&clean=True&charttype=pie

Where:
  • username is your Python Anywhere user name.
  • event_name is the mixpanel event name, just replace space chararcters with underscores
  • apisecretkey is your mixpanel secret key
  • apikey is your mixpanel project api key
  • (optional) propertyname is one of the event's property (space are replaced with underscores) to segment on
  • (optional) clean is just a way to remove data with empty or undefined value for the segmented property
  • charttype allows to select good formatting depending on which kind of plot you select (currently I just implemented lines and pie). If not specified lines is the default.

Result

How to configure your "Private Url" widget
4 widgets using "my" mixpanel connector (5 widgets allowed with free cyfe plan)

Conclusion

With a very little programming anyone can leverage the power of Cyfe and Mixpanel. Anyway it seems that Cyfe is quite active and probably already working on improvements for the Mixpanel widget. So my connector may be useless soon, but in the mean time I will use it.

I'm now working on worldmap charts to display Mixpanel data, but that will be the purpose of another article.

Code


flask_app.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!flask/bin/python
from datetime import date, timedelta
from flask import Flask
from flask import request

import mixpanel
import cyfe

app = Flask(__name__)

def get_period(start, end):
    today = date.today()
    startdate = today - timedelta(start)
    enddate = today - timedelta(end)

    return (str(startdate), str(enddate))

@app.route('/mixpanel/api/v1.0/<event>/<apisecret>/<apikey>', methods=['GET'])
def get_event(event, apisecret, apikey):
    api = mixpanel.API(api_key = apikey, api_secret = apisecret)
    period = get_period(30, 1)
    event = event.replace('_', ' ')
    by = request.args.get('by', 'None')
    charttype = request.args.get('charttype', 'None')

    if by != None and by != '' and by != 'none':
        by = by.replace('_', ' ')
        data = cyfe.get_segmentation(api, event, by, period[0], period[1])
    else:
        data = cyfe.get_event(api, event, period[0], period[1])

    clean = request.args.get('clean')

    return cyfe.mixpaneldata_to_piecyfe(data, clean) if charttype=='pie' else cyfe.mixpaneldata_to_cyfe(data, clean)

# Uncomment this line to run in local
#app.run(debug=True)
cyfe.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def get_event(api, eventName, from_date, to_date):
    return api.request(['events'], {
    'event' : [eventName,],
    'type' : 'general',
    'unit' : 'day',
    'interval' : 10,
    'from_date': from_date,
    'to_date': to_date,
    })

def get_segmentation(api, eventName, property, from_date, to_date):
    return api.request(['segmentation'], {
    'event' : eventName,
    'type' : 'general',
    'unit' : 'day',
    'on': 'properties["%s"]' % property,
    'from_date': from_date,
    'to_date': to_date,
    })

def mixpaneldate_to_cyfe(date):
    return date.replace('-', '')

def mixpaneldata_to_cyfe(data, clean=True):
    series = data['data']['series']
    values = data['data']['values']

    # remove unwanted values
    if clean:
        values.pop('', None)
        values.pop('undefined', None)

    fieldnames = ['Date'] + values.keys()
    csvText = ','.join(fieldnames) + '\n'
    for i in range(0, len(series)):
        date = series[i]
        cyfeDate = mixpaneldate_to_cyfe(date)
        csvText = csvText + cyfeDate + ','
        for serie in values.items():
            csvText = csvText + str(serie[1][date]) + ','
        csvText = csvText + '\n'

    return csvText

def mixpaneldata_to_piecyfe(data, clean=True):
    values = data['data']['values']

    # remove unwanted values
    if clean:
        values.pop('', None)
        values.pop('undefined', None)

    csvText = ','.join(values.keys()) + '\n'
    for item in values.items():
        csvText = csvText + str(sum(item[1].values())) + ','
    csvText = csvText + '\n'

    return csvText
mixpanel.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
#! /usr/bin/env python
#
# Mixpanel, Inc. -- http://mixpanel.com/
#
# Python API client library to consume mixpanel.com analytics data.
#
# Copyright 2010-2013 Mixpanel, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import hashlib
import urllib
import urllib2
import time
try:
    import json
except ImportError:
    import simplejson as json

class API(object):

    ENDPOINT = 'https://mixpanel.com/api'
    VERSION = '2.0'

    def __init__(self, api_key, api_secret):
        self.api_key = api_key
        self.api_secret = api_secret

    def request(self, methods, params, format='json'):
        """
            methods - List of methods to be joined, e.g. ['events', 'properties', 'values']
                      will give us http://mixpanel.com/api/2.0/events/properties/values/
            params - Extra parameters associated with method
        """
        params['api_key'] = self.api_key
        params['expire'] = int(time.time()) + 600   # Grant this request 10 minutes.
        params['format'] = format
        if 'sig' in params: del params['sig']
        params['sig'] = self.hash_args(params)

        request_url = '/'.join([self.ENDPOINT, str(self.VERSION)] + methods) + '?' + self.unicode_urlencode(params)
        request = urllib2.urlopen(request_url, timeout=120)
        data = request.read()

        return json.loads(data)

    def unicode_urlencode(self, params):
        """
            Convert lists to JSON encoded strings, and correctly handle any
            unicode URL parameters.
        """
        if isinstance(params, dict):
            params = params.items()
        for i, param in enumerate(params):
            if isinstance(param[1], list):
                params[i] = (param[0], json.dumps(param[1]),)

        return urllib.urlencode(
            [(k, isinstance(v, unicode) and v.encode('utf-8') or v) for k, v in params]
        )

    def hash_args(self, args, secret=None):
        """
            Hashes arguments by joining key=value pairs, appending a secret, and
            then taking the MD5 hex digest.
        """
        for a in args:
            if isinstance(args[a], list): args[a] = json.dumps(args[a])

        args_joined = ''
        for a in sorted(args.keys()):
            if isinstance(a, unicode):
                args_joined += a.encode('utf-8')
            else:
                args_joined += str(a)

            args_joined += '='

            if isinstance(args[a], unicode):
                args_joined += args[a].encode('utf-8')
            else:
                args_joined += str(args[a])

        hash = hashlib.md5(args_joined)

        if secret:
            hash.update(secret)
        elif self.api_secret:
            hash.update(self.api_secret)
        return hash.hexdigest()

lundi 29 septembre 2014

Pure xaml star rating control

One of the best feature in wpf/xaml is ability to completely redesign a built-in control without any line of code. Most of the time you will just define a custom theme for a selection of useful controls. But it becomes more interesting when "hacking" an existing control to make something new.

In my day work I had to make a star rating control and I thought it would be interesting to reuse the slider control for that purpose. I also wanted it to be pure xaml. I use a shape proposed by Kiran Kumar which I slightly modified.
Here is a visual (nothing really surprising though):
If you want to try, simply copy/paste the following piece of xaml to your favorite xaml editor (I used Kaxaml, which is a great tool):

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Background="#2d2d20">
  <Page.Resources>
    <SolidColorBrush x:Key="NoStar" Color="#29ffffff" />
    <SolidColorBrush x:Key="Star" Color="Yellow" />

    <Style
      x:Key="StarRatingShapeStyle"
      TargetType="Polygon">
      <Setter Property="Width" Value="12" />
      <Setter Property="Height" Value="12" />
      <Setter Property="Fill" Value="{StaticResource NoStar}" />
      <Setter Property="Points" Value="9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7" />
      <Setter Property="Stretch" Value="Fill" />
      <Setter Property="Stroke" Value="White" />
      <Setter Property="StrokeLineJoin" Value="Round" />
      <Setter Property="StrokeThickness" Value=".5" />
      <Setter Property="IsHitTestVisible" Value="False" />
    </Style>

    <Style x:Key="RatingSliderRepeatButtonStyle" TargetType="{x:Type RepeatButton}">
      <Setter Property="Padding" Value="0" />
      <Setter Property="Margin" Value="0" />
      <Setter Property="Background" Value="Transparent" />
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate>
            <Border
              Background="{TemplateBinding Background}"
              BorderThickness="0"
              />
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

    <Style x:Key="RatingSliderThumbStyle" TargetType="{x:Type Thumb}">
      <Setter Property="OverridesDefaultStyle" Value="true" />
      <Setter Property="Focusable" Value="False" />
      <Setter Property="Padding" Value="0" />
      <Setter Property="Margin" Value="0" />
      <Setter Property="Background" Value="Transparent" />
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type Thumb}">
            <Border
              Background="{TemplateBinding Background}"
              BorderThickness="0"
              />
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

    <Style
      x:Key="StarRatingSliderStyle"
      TargetType="Slider">
      <Setter Property="Minimum" Value="0" />
      <Setter Property="Maximum" Value="5" />
      <Setter Property="SmallChange" Value="1" />
      <Setter Property="LargeChange" Value="1" />
      <Setter Property="TickFrequency" Value="1" />
      <Setter Property="IsSnapToTickEnabled" Value="True" />
      <Setter Property="IsMoveToPointEnabled" Value="True" />
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate
            TargetType="Slider">
            <Grid>
              <Track
                x:Name="PART_Track"
                >
                <Track.DecreaseRepeatButton>
                  <RepeatButton
                    Style="{StaticResource RatingSliderRepeatButtonStyle}"
                    Command="{x:Static Slider.DecreaseLarge}"
                    />
                </Track.DecreaseRepeatButton>
                <Track.IncreaseRepeatButton>
                  <RepeatButton
                    Style="{StaticResource RatingSliderRepeatButtonStyle}"
                    Command="{x:Static Slider.IncreaseLarge}"
                    />
                </Track.IncreaseRepeatButton>
                <Track.Thumb>
                  <Thumb
                    x:Name="PART_Thumb"
                    Style="{StaticResource RatingSliderThumbStyle}"
                    />
                </Track.Thumb>
              </Track>

              <StackPanel
                Orientation="{TemplateBinding Orientation}">
                <Polygon
                  x:Name="S5"
                  Style="{StaticResource StarRatingShapeStyle}"
                  />
                <Polygon
                  x:Name="S4"
                  Style="{StaticResource StarRatingShapeStyle}"
                  />
                <Polygon
                  x:Name="S3"
                  Style="{StaticResource StarRatingShapeStyle}"
                  />
                <Polygon
                  x:Name="S2"
                  Style="{StaticResource StarRatingShapeStyle}"
                  />
                  <Polygon
                  x:Name="S1"
                  Style="{StaticResource StarRatingShapeStyle}"
                  />
              </StackPanel>
            </Grid>

            <ControlTemplate.Triggers>
              <Trigger Property="Value" Value="1">
                <Setter TargetName="S1" Property="Fill" Value="{StaticResource Star}"/>
                <Setter TargetName="S2" Property="Fill" Value="{StaticResource NoStar}"/>
                <Setter TargetName="S3" Property="Fill" Value="{StaticResource NoStar}"/>
                <Setter TargetName="S4" Property="Fill" Value="{StaticResource NoStar}"/>
                <Setter TargetName="S5" Property="Fill" Value="{StaticResource NoStar}"/>
              </Trigger>
              <Trigger Property="Value" Value="2">
                <Setter TargetName="S1" Property="Fill" Value="{StaticResource Star}"/>
                <Setter TargetName="S2" Property="Fill" Value="{StaticResource Star}"/>
                <Setter TargetName="S3" Property="Fill" Value="{StaticResource NoStar}"/>
                <Setter TargetName="S4" Property="Fill" Value="{StaticResource NoStar}"/>
                <Setter TargetName="S5" Property="Fill" Value="{StaticResource NoStar}"/>
              </Trigger>
              <Trigger Property="Value" Value="3">
                <Setter TargetName="S1" Property="Fill" Value="{StaticResource Star}"/>
                <Setter TargetName="S2" Property="Fill" Value="{StaticResource Star}"/>
                <Setter TargetName="S3" Property="Fill" Value="{StaticResource Star}"/>
                <Setter TargetName="S4" Property="Fill" Value="{StaticResource NoStar}"/>
                <Setter TargetName="S5" Property="Fill" Value="{StaticResource NoStar}"/>
              </Trigger>
              <Trigger Property="Value" Value="4">
                <Setter TargetName="S1" Property="Fill" Value="{StaticResource Star}"/>
                <Setter TargetName="S2" Property="Fill" Value="{StaticResource Star}"/>
                <Setter TargetName="S3" Property="Fill" Value="{StaticResource Star}"/>
                <Setter TargetName="S4" Property="Fill" Value="{StaticResource Star}"/>
                <Setter TargetName="S5" Property="Fill" Value="{StaticResource NoStar}"/>
              </Trigger>
              <Trigger Property="Value" Value="5">
                <Setter TargetName="S1" Property="Fill" Value="{StaticResource Star}"/>
                <Setter TargetName="S2" Property="Fill" Value="{StaticResource Star}"/>
                <Setter TargetName="S3" Property="Fill" Value="{StaticResource Star}"/>
                <Setter TargetName="S4" Property="Fill" Value="{StaticResource Star}"/>
                <Setter TargetName="S5" Property="Fill" Value="{StaticResource Star}"/>
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </Page.Resources>
  <StackPanel>
    <Slider
      x:Name="Ranking"
      Orientation="Vertical"
      HorizontalAlignment="Center"
      Background="Transparent"
      Style="{StaticResource StarRatingSliderStyle}"
      />
    <Slider
      x:Name="Test"
      Orientation="Horizontal"
      Minimum="0"
      Maximum="5"
      Width="200"
      Height="200"
      IsSnapToTickEnabled="True"
      TickFrequency="1"
      Value="{Binding Value, ElementName=Ranking}"
      />
  </StackPanel>
</Page>

This xaml implementation is not completely usable because of two small issues:

  • Depending on where you click on a star (above or below the middle) you got different behaviors. But it could be easily solved with a converter that would ceil or floor the value.
  • Horizontal and vertical orientation is handled, but in horizontal orientation the order is reversed. I guess I should not use the Stackpanel orientation but rather a LayoutTransform.

dimanche 28 septembre 2014

2D fractal heightmap

3D Inspiration

I recently had to generate a random 2D heightmap for a spare time project.
Then I recalled Paul Bourke excellent page about fractals (and a lot more !)

You will find a great article on how to generate 3D planets with a very simple algorithm here. In an ancient time I implemented this algorithm in a 3DS Max plugin. And today I thought it would be funny to transpose it to 2D and see what happens ...

Let me quickly describe the 3D algorithm in a few steps:
  1. Start with a sphere
  2. Pick a random plane (actually you need a normal to that plane)
  3. Increase height on one side and decrease height on the other side of the plan
  4. Go back to step 2
Illustrations from Paul Bourke (1000 iterations)

2D Transposition


Let's now talk about how we can transpose this algorithm to 2D:
  1. Start with a flat map (square or rectangular)
  2. Pick a random line that cut the map (you need two points or a point and a normal)
  3. Randomly increase height on one side of the line and decrease height on the other side
  4. Go back to step 2.



Some results after 10000 iterations on a 512x512 grid (12s on my core i7)

Indeed the more iteration the more "details" you get but the downside is that it takes longer to generate (for each iteration all pixels are evaluated). Then I had the idea to try a different workflow:
  1. Pick a random "normal" associated with a random height offset and save it
  2. Go back to step 1 as many times as number of iteration
  3. For every pixel sum the height offset given by each line
I naively I though it would be faster, but indeed it produce exactly the same amount of processing. There is at least one benefit to this method, being able to calculate elevation for any coordinate (the map size can grow at any time).

Generalization

After playing a bit with my generator, I thought it could funny to go a bit further. What happens if instead of dividing the map in two part with a line, I choose a different primitive like a circle.
And well, why not a triangle or square?

Here are some outputs using a random combination of lines, circles, triangles and rectangles.
As you can see other shapes introduce a huge bias, most probably because I use circles, rectangles and triangles that fully lie inside the grid. I will try to find a better way to define those shapes. But the good thing it that it seems to remove visible line patterns (that what led me to this idea).

Updated (September 30th 2014)

At the cost of a lot more iterations, and using a square grid instead of a rectangular one the visual results seem good. The performance are not so good even with a small change I did to take advantage of parallel computing (4x times faster on a quad core).

1
2
3
4
5
Parallel.ForEach(
      (from x in Enumerable.Range(0, this.width)
       from y in Enumerable.Range(0, this.height)
       select new Tuple<int, int>(x, y)),
        p => this.map[p.Item2, p.Item1] = this.dividers.Sum(s => s.GetValue(p.Item1, p.Item2)));

I didn't understood it first, but using a rectangular grid introduce a small bias in the density of horizontal and vertical lines that was generating more visible line patterns.

In the end I'm a bit disappointed by the visual results. Indeed increasing the number of iterations to 1000 reduce artifacts at the cost of generation time.

Below you will find a piece of code from a visual studio demo project:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace HeightMapDemo
{
  public class PaulBourke
  {
    private interface IDivider
    {
      double GetValue(double x, double y);
    }

    private class Splitter : IDivider
    {
      public Splitter(Point a, double va, Point b, double vb)
      {
        this.X = (int)a.X;
        this.Y = (int)a.Y;
        this.va = va;
        this.vb = vb;

        this.ratio = (b.X - this.X) / (b.Y - this.Y);
      }

      private readonly int X;
      private readonly int Y;
      private readonly double va;
      private readonly double vb;
      private readonly double ratio;

      public double GetValue(double x, double y)
      {
        return this.ratio * (y - this.Y) - (x - this.X) > 0 ? this.va : this.vb;
      }
    }

    private readonly Random randomizer;
    private readonly int width;
    private readonly int height;
    private readonly double[,] map;

    private IDivider[] dividers;

    public PaulBourke(Random randomizer, int width, int height)
    {
      this.randomizer = randomizer;
      this.width = width;
      this.height = height;
      this.map = new double[height, width];
      this.Iterations = 10000;
    }

    public int Iterations { get; set; }

    public double[,] Map
    {
      get { return this.map; }
    }

    public IEnumerable<double> Map1D
    {
      get { return this.map.Cast<double>(); }
    }

    public void Generate()
    {
      this.Prepare();

      Parallel.ForEach(
      (from x in Enumerable.Range(0, this.width)
       from y in Enumerable.Range(0, this.height)
       select new Tuple<int, int>(x, y)),
        p => this.map[p.Item2, p.Item1] = this.dividers.Sum(s => s.GetValue(p.Item1, p.Item2)));
    }

    private void Prepare()
    {
      this.dividers = new IDivider[this.Iterations];

      Parallel.ForEach(Enumerable.Range(0, this.Iterations),
                        i => this.dividers[i] = this.CreateSplitter());
    }

    private IDivider CreateSplitter()
    {
      int sideA = this.randomizer.Next(4);
      Point pointA = this.PickPointOnSide(sideA);

      int sideB = this.Next(4, sideA);
      Point pointB = this.PickPointOnSide(sideB);
      while (pointB == pointA)
      {
        pointB = this.PickPointOnSide(sideB);
      }

      double valueA = 2 * this.randomizer.NextDouble() - 1d;
      double valueB = -valueA;// this.randomizer.NextHeight();

      return new Splitter(pointA, valueA, pointB, valueB);
    }

    private Point PickPointOnSide(int side)
    {
      Point p = new Point();

      switch (side)
      {
        case 0:
          p.Y = 0.0;
          p.X = this.randomizer.Next(this.width);
          break;
        case 1:
          p.Y = this.randomizer.Next(this.height);
          p.X = this.width;
          break;
        case 2:
          p.Y = this.width;
          p.X = this.randomizer.Next(this.width);
          break;
        case 3:
          p.Y = this.randomizer.Next(this.height);
          p.X = 0.0;
          break;
      }

      return p;
    }

    private int Next(int maxValue, int except)
    {
      int value = this.randomizer.Next(maxValue);

      while (value == except)
      {
        value = this.randomizer.Next(maxValue);
      }

      return value;
    }
  }
}

Updated (October 3rd 2014)

Fuzzyness

I was wondering how I could achieve good results with less iterations. Because I must admit that less 5K iterations produces visible edges (even if I mix different primitives). Then I though that instead of splitting the grid with hard edges I could introduce "fuzzy" edges.

How I made it ? Using perlin noise to scramble edges. Basically if a point is close enough to an edge, instead of being binary categorized, a 2D perlin noise is used to categorize. Below you can see the code for a line divider:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public override double GetValue(Point p)
{
  Vector v = this.origin - p;

  if (this.UseEdge)
  {
    return Math.Abs(v.X * this.normal.X + v.Y * this.normal.Y) < 0.5 ? 10d : 0d;
  }
  
  double distance = v.X * this.normal.X + v.Y * this.normal.Y;

  if (this.Fuzziness > 0 && Math.Abs(distance) < this.Fuzziness)
  {
    double generate = this.Fuzziness*this.PerlinNoise.Generate(p.X, p.Y);

    return generate > distance ? this.va : this.vb;
  }

  return distance < 0 ? this.va : this.vb;
}

In the code above you can see:
  •  a toggle to display only the line itself
  • the line is defined by its normal and a point on the grid
  • the distance is actually the result of a dot product
  • if the point is in the fuzziness area then "randomize" using perlin noise the point value.
This algorithm seems to remove hard edges artifacts but, well introduce other kind of artifacts (depending on the perlin noise parameters). But here is a nice result with less than 1000 iterations: