Add page to analyze differences in the distribution of users in different channels

This commit is contained in:
Marco Castelluccio 2017-01-23 16:36:06 +00:00
Родитель 74556e1dd2
Коммит eb0bdff46d
6 изменённых файлов: 270 добавлений и 4 удалений

27
channels_diff.html Normal file
Просмотреть файл

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Analyze distribution</title>
<link rel="stylesheet" href="style.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js" integrity="sha256-dsOXGNHAo/syFnazt+KTBsCQeRmlcW1XKL0bCK4Baec=" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.4/js/standalone/selectize.js" integrity="sha256-YhmtrYJDSZgZFL8jDCR8VNOV/iigJPZYNCk8L9J72Hk=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.4/css/selectize.css" integrity="sha256-HzWsbetzuScwBVnRYZIRJeXPQjHvyAMWhukery/8L8A=" crossorigin="anonymous" />
</head>
<body>
<script src="correlations.js" type="text/javascript"></script>
<script src="channels_diff.js" type="text/javascript"></script>
<select name="property" id="property"></select>
<select name="value" id="value"></select>
<table id="table">
<tr>
<th>Percentage in nightly</th>
<th>Percentage in aurora</th>
<th>Percentage in beta</th>
<th>Percentage in release</th>
</tr>
</table>
<svg id="image" width="1200" height="900"></svg>
</body>
</html>

212
channels_diff.js Normal file
Просмотреть файл

@ -0,0 +1,212 @@
let options = {
'property': {
value: null,
type: 'option',
},
'value': {
value: null,
type: 'option',
},
};
function getOption(name) {
return options[name].value;
}
function getOptionType(name) {
return options[name].type;
}
function setOption(name, value) {
return options[name].value = value;
}
let onLoad = new Promise(function(resolve, reject) {
window.onload = resolve;
});
function setURL() {
let url = new URL(location.href);
let property = getOption('property');
let value = getOption('value');
if (!property) {
url.search = '';
} else if (!value) {
url.search = '?property=' + property;
} else {
url.search = '?property=' + property + '&value=' + value;
}
history.replaceState({}, document.title, url.href);
}
function populateTable() {
let property = getOption('property');
let value = getOption('value');
if (!property || !value) {
return;
}
let channels = ['nightly', 'aurora', 'beta', 'release'];
let results = {};
Promise.all(
channels
.map((channel, index) =>
correlations.getChannelsPercentage('Firefox', channel, property, value)
.then(result => results[channel] = result)
)
)
.then(() => {
let table = document.getElementById('table');
while (table.rows.length > 1) {
table.deleteRow(table.rows.length - 1);
}
let row = table.insertRow(table.rows.length);
channels.forEach((channel, index) => {
let cell = row.insertCell(index);
cell.appendChild(document.createTextNode(correlations.toPercentage(results[channel] && results[channel].p || 0) + ' %'));
});
let svgElem = document.getElementById('image');
d3.select(svgElem).selectAll('*').remove();
let margin = { top: 20, right: 300, bottom: 70, left: 300 };
let width = svgElem.getAttribute('width') - margin.left - margin.right;
let height = svgElem.getAttribute('height') - margin.top - margin.bottom;
let x = d3.scale.ordinal().rangeRoundBands([0, width], .05);
let y = d3.scale.linear().range([height, 0]);
let color = d3.scale.ordinal()
.range(['black', 'blue', 'red', 'orange']);
let xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
let yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10);
var svg = d3.select(svgElem)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
let data = channels.map(channel => {
return {
channel: channel,
value: (results[channel] && results[channel].p || 0) * 100,
};
});
x.domain(channels);
y.domain([0, d3.max(data, function(d) { return d.value; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(27," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 3)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Percentage of users (%)");
svg.selectAll("bar")
.data(data)
.enter()
.append("rect")
.style('fill', d => color(d.channel))
.attr("x", function(d) { return 27 + x(d.channel); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
});
}
function populateValueSelect() {
let property = getOption('property');
if (!property) {
return;
}
correlations.getChannelsValues('Firefox', property)
.then(values => {
if ($('#value')[0].selectize) {
$('#value')[0].selectize.destroy();
}
let $select = $('#value').selectize({
valueField: 'name',
labelField: 'name',
searchField: 'name',
options: values.map(value => {
return {'name': value};
}),
onChange: value => {
setOption('value', value);
setURL();
populateTable();
},
});
$select[0].selectize.setValue(getOption('value'));
});
}
onLoad
.then(() => correlations.getChannelsProperties('Firefox'))
.then(props => {
let queryVars = new URL(location.href).search.substring(1).split('&');
Object.keys(options)
.forEach(function(optionName) {
for (let queryVar of queryVars) {
if (queryVar.startsWith(optionName + '=')) {
let option = queryVar.substring((optionName + '=').length).trim();
setOption(optionName, decodeURIComponent(option));
}
}
});
let $select = $('#property').selectize({
valueField: 'name',
labelField: 'name',
searchField: 'name',
options: props.map(prop => {
return {'name': prop};
}),
onChange: prop => {
if (prop != getOption('property')) {
setOption('value', '');
}
setOption('property', prop);
setURL();
populateValueSelect();
},
});
$select[0].selectize.setValue(getOption('property'));
})
.catch(function(err) {
console.error(err);
});

Просмотреть файл

@ -359,6 +359,27 @@ var correlations = (() => {
});
}
function getChannelsProperties(product) {
return loadChannelsDifferencesData(product)
.then(() => ['release', 'beta', 'aurora', 'nightly'].map(channel => channelsData[channel].map(longitudinalElem => Object.keys(longitudinalElem.item)[0])))
.then(all_props_per_channel => [].concat.apply([], all_props_per_channel))
.then(all_props => new Set(all_props))
.then(props => Array.from(props));
}
function getChannelsValues(product, property) {
return loadChannelsDifferencesData(product)
.then(() => ['release', 'beta', 'aurora', 'nightly'].map(channel => channelsData[channel].filter(longitudinalElem => Object.keys(longitudinalElem.item).indexOf(property) != -1).map(longitudinalElem => longitudinalElem.item[property])))
.then(all_values_per_channel => [].concat.apply([], all_values_per_channel))
.then(all_values => new Set(all_values))
.then(values => Array.from(values));
}
function getChannelsPercentage(product, channel, property, value) {
return loadChannelsDifferencesData(product)
.then(() => channelsData[channel].find(longitudinalElem => Object.keys(longitudinalElem.item).indexOf(property) != -1 && longitudinalElem.item[property] == value));
}
function text(textElem, signature, channel, product, show_ci=false) {
loadCorrelationData(signature, channel, product)
.then(data => {
@ -522,6 +543,9 @@ var correlations = (() => {
getAnalysisDate: getAnalysisDate,
text: text,
rerank: rerank,
getChannelsProperties: getChannelsProperties,
getChannelsValues: getChannelsValues,
getChannelsPercentage: getChannelsPercentage,
toPercentage: toPercentage,
graph: graph,
};

Просмотреть файл

@ -354,6 +354,7 @@ if __name__ == "__main__":
'supergraph.html', 'supergraph.js',
'rerank.html', 'rerank.js',
'common_landings.html', 'common_landings.js',
'channels_diff.html', 'channels_diff.js',
] + ['images/' + image for image in os.listdir('images')]
for f in files:

Двоичные данные
images/channels_diff.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 23 KiB

Просмотреть файл

@ -30,13 +30,15 @@ td {
</tr>
<tr>
<td><a href="buildid_changeset.html">Build ID <-> Changeset conversion</a></td>
<td><img src="images/buildid_changeset.png" alt="Build ID <-> Changeset conversion" width="192" height="16" /></td>
<td><img src="images/buildid_changeset.png" alt="Build ID <-> Changeset conversion" width="192" height="32" /></td>
<td><a href="common_landings.html">Find common landings between channels</a></td>
<td><img src="images/common_landings.png" alt="Find common landings between channels" width="192" height="96" /></td>
<td><a href="addon_related_signatures.html">List of addon-related signatures</a></td>
<td><img src="images/addon_related_signatures.png" alt="List of addon-related signatures" width="192" height="96" /></td>
<td><img src="images/common_landings.png" alt="Find common landings between channels" width="192" height="64" /></td>
<td><a href="channels_diff.html">Analyze distribution differences between channels</a></td>
<td><img src="images/channels_diff.png" alt="Analyze distribution differences between channels" width="192" height="96" /></td>
</tr>
<tr>
<td><a href="addon_related_signatures.html">List of addon-related signatures</a></td>
<td><img src="images/addon_related_signatures.png" alt="List of addon-related signatures" width="192" height="96" /></td>
<td><a href="graphics_critical_errors.html">List of graphics critical errors</a></td>
<td><img src="images/graphics_critical_errors.png" alt="List of graphics critical errors" width="192" height="96" /></td>
</tr>